forge strategy¶
Create, register, validate, and manage strategy JSON definitions. Covers scaffolding from built-in templates, local registration, viewing, JSON → DB migration, deletion, and logical consistency checks (static + dynamic).
About sample output
Sample outputs in this page are based on the formats read from the alpha-forge source. Actual numbers depend on the data and environment.
Subcommands¶
| Command | Description |
|---|---|
forge strategy list |
List all registered strategies |
forge strategy create |
Create a JSON file from a built-in template |
forge strategy save |
Register a custom strategy from a JSON file |
forge strategy show |
Display the definition (JSON) of a registered strategy |
forge strategy migrate |
Import existing JSON files into the DB |
forge strategy delete |
Delete a registered strategy from the DB |
forge strategy purge |
Purge the strategy JSON, related results, and DB entry in a single command |
forge strategy validate |
Validate strategy logical consistency |
forge strategy signals |
Count entry signals for a strategy |
forge strategy list¶
List all registered strategies. When config.strategies.use_db is true, reads from the DB; otherwise from the file-based store.
Synopsis¶
Arguments and options¶
None.
Sample output¶
ID Name Version Timeframe
------------------------------------------------------------------------------------------
spy_sma_crossover_v1 SMA Golden/Death Cross SPY v1 1.0.0 1d
qqq_hmm_macd_ema_rsi_v1 QQQ HMM × MACD × EMA × RSI v1 1.0.0 1d
gc_hmm_macd_ema_v1 GC HMM × MACD × EMA v1 1.0.0 1d
When no strategies are registered:
forge strategy create¶
Create a strategy JSON file from a built-in template. Does not register the strategy — edit the file and then call forge strategy save.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
--template |
required | - | Built-in template name to use as base |
--out |
required | - | Output JSON file path |
Available templates¶
Built-in templates from alpha-forge/src/alpha_forge/strategy/templates.py (_TEMPLATE_REGISTRY):
| Name | Description |
|---|---|
sma_crossover_v1 |
Short SMA × long SMA golden / death cross |
rsi_reversion_v1 |
Mean reversion using RSI overbought / oversold |
macd_crossover_v1 |
MACD line × signal line crossover |
bbands_breakout_v1 |
Bollinger Bands upper-band breakout |
range_reversion_v1 |
Range-bound mean reversion |
supertrend_adx_v1 |
Trend-following with SuperTrend + ADX |
ema_adx_macd_v1 |
Composite trend strategy with EMA + ADX + MACD |
hmm_range_pure_v1 |
Range-pure with HMM regime detection |
hmm_anomaly_v1 |
Anomaly detection via HMM regime |
macd_reversal_v1 |
MACD-based turning-point reversal |
grid_bot_template |
Grid bot template |
connors_rsi2_v1 |
Connors RSI-2 mean reversion (issue #475 Phase 2). Ported from Larry Connors "Short-Term Trading Strategies That Work". Long when SMA(200) up + RSI(2) < 10, exit on SMA(5) cross-up. 70-85% win rate verified on SPY/QQQ (Note: designed for daily equities; FX 1h adoption requires SMA period re-scaling) |
donchian_turtle_v1 |
Donchian Channel Breakout (Turtle) (issue #475 Phase 2). Ported from Richard Dennis "Turtle Trading Rules". Long on 20-period high break, exit on 10-period low cross-down. 45% win rate verified on daily futures/equities (Note: designed for daily timeframes; FX 1h adoption requires length re-scaling) |
kama_rsi_v1 |
KAMA + RSI regime-adaptive (issue #475 Phase 2). Ported from Perry Kaufman "New Trading Systems and Methods" (2013). KAMA auto-detects trend/range; RSI captures overbought/oversold. FX 1h validation: MDD 46-76% (better than prior two) but CAGR still negative |
tsi_reversion_v1 |
TSI Mean Reversion (issue #475 Phase 2). Ported from Daniel Requejo (2024) SSRN paper "Efficacy of a Mean Reversion Trading Strategy Using TSI". Long when TSI < -25 + signal cross-up, short when TSI > +25 + cross-down. Verified on SPY/QQQ. FX 1h validation: MDD 82-97% with negative CAGR |
connors_rsi2_fx1h_v1 |
Connors RSI-2 FX 1h variant (issue #480). SMA(200)/SMA(5) re-scaled to SMA(480)/SMA(24). FX 1h validation: trades 350+ but MDD 99-100% (still broken) |
donchian_turtle_fx1h_v1 |
Donchian Turtle FX 1h variant (issue #480). length 20/10 re-scaled to 120/60. FX 1h validation: MDD 13-96% (large variance) |
kama_rsi_fx1h_v1 |
KAMA + RSI FX 1h variant (issue #480). length 10/slow 30 → 48/120 re-scaled. 🎯 EURUSD reached CAGR +0.54% / MDD 7.95% (only positive CAGR across 4 pairs) |
tsi_reversion_fx1h_v1 |
TSI Reversion FX 1h variant (issue #480). fast/slow/signal re-scaled from 13/25/13 to 48/120/48. FX 1h validation: trades 0-4 (over-filtered) |
kama_rsi_fx1h_v2 |
KAMA+RSI FX 1h, RSI loose (issue #482). v1 RSI 35/65 → 45/55. FX 1h validation: trades 215+ (~35× of v1) but MDD 93-99% (over-relaxed) |
kama_rsi_fx1h_v3 |
KAMA+RSI FX 1h, fast KAMA (issue #482). length 48/slow 120 → 24/60. FX 1h validation: trades 2-9 / MDD 8-36% / CAGR negative |
kama_rsi_mtf_v1 |
KAMA+RSI + 4h trend filter MTF variant (issue #484). Adds a 4h EMA(50) AND filter to kama_rsi_fx1h_v1, allowing 1h entries only when the higher timeframe trend agrees. The engine resamples 4h series from 1h data automatically — no extra fetch required |
donchian_turtle_mtf_v1 |
Donchian Turtle + 4h trend filter MTF variant (issue #484). Adds a 4h EMA(50) AND filter to donchian_turtle_fx1h_v1. Long-only in uptrends and short-only in downtrends to suppress trend-following drawdowns |
kama_rsi_mtf_atr_v1 |
KAMA + RSI + 4h trend + ATR SL MTF variant (issue #486). Adds an ATR(14) × 2 entry-locked stop loss (lock_on_entry=true) to kama_rsi_mtf_v1. Aims to improve win rate and CAGR while keeping low trade count |
kama_rsi_mtf_atr_v2 |
KAMA-exit-removed variant (issue #489). Fixes v1's dead ATR SL by removing close < kama from exits, simplifying to RSI (take profit) + ATR SL (hard stop). KAMA is used purely as an entry trend filter |
kama_rsi_mtf_trail_v1 |
True trailing-SL variant (issue #488). Replaces v2's fixed ATR SL with risk_management.trailing_stop_pct=1.0, enabling vectorbt's sl_trail=True for a true "ratchet up only" trailing stop |
kama_rsi_mtf_trail_v2 |
Tight trailing-SL variant (issue #492). Tightens trail_v1's trailing_stop_pct from 1.0 to 0.5 (FX 1h ATR ≈ 0.3-0.5%) to suppress MDD |
kama_rsi_mtf_trail_loose_v1 |
High-frequency variant (issue #495). Loosens trail_v1's RSI thresholds from 35/65 to 40/60 to lift trades from 4-7 to 8-12, targeting min_trades and vol filter pass at the cost of 1-3pt MDD |
kama_rsi_mtf_trail_loose_v2 |
High-frequency + tight 0.5% trailing (issue #497). Combines trail_loose_v1 (RSI 40/60) with trail_v2 (trailing 0.5%) to preserve trades / vol gains while reducing MDD back into the 30-40% range |
kama_rsi_mtf_trail_loose_kama_short_v1 |
Short KAMA (24/60) variant (issue #502). Shortens trail_loose_v2's KAMA from 48/120 to 24/60 for sharper overheating detection, targeting win rate / profit_factor improvement |
kama_rsi_mtf_trail_loose_tp_v1 |
Take-profit variant (issue #504). Adds take_profit_pct=2.0 on top of trail_loose_v2 for a 4:1 (TP +2% : SL -0.5%) ratio structure targeting profit_factor above 1.0 |
kama_rsi_mtf_trail_v15_tp_v1 |
Trail 2.0% + TP 1.5% design (issue #506). Fixes trail_loose_tp_v1's TP preemption issue by placing TP (1.5) inside the trailing range (2.0). Ensures TP firing for profit_factor improvement |
Sample output¶
Common errors¶
| Situation | Behavior |
|---|---|
| Unknown template name | Raises ValueError: Unknown template name: <name>. Available: ... |
forge strategy save¶
Register a custom strategy in the strategy registry from a JSON file. When config.journal.auto_record is true, a Journal snapshot is also recorded.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
FILE_PATH |
argument (required) | - | Strategy JSON file path |
--force |
flag | false | Overwrite if a strategy with the same ID already exists |
Sample output¶
When overwritten with --force:
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: file not found - <path> |
File missing | Verify the path |
Error: <DuplicateStrategyError> |
Same ID already registered | Use --force, or change strategy_id in the JSON |
forge strategy show¶
Pretty-print a registered strategy JSON to stdout.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
STRATEGY_ID |
argument (required) | - | Strategy ID to display |
Sample output¶
=== spy_sma_crossover_v1 ===
{
"strategy_id": "spy_sma_crossover_v1",
"name": "SMA Golden/Death Cross SPY v1",
"version": "1.0.0",
...
}
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: strategy '<id>' not found |
Invalid ID | Verify with forge strategy list |
forge strategy migrate¶
Import existing JSON files under config.strategies.path into the DB (SQLite). Use this when switching to the use_db: true operation mode.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
--dry-run |
flag | false | Preview only, no writes |
--force |
flag | false | Overwrite duplicate IDs with the last file |
Prerequisite¶
config.strategies.use_db must be true. In forge.yaml (FORGE_CONFIG):
Sample output¶
⚠️ Duplicate strategy_id detected:
spy_sma_crossover_v1:
- spy_sma_crossover_v1.json
- spy_sma_crossover_v1_optimized.json
Re-run with --force to overwrite with the last file.
With --force --dry-run:
Normal run:
Skip (broken_v1.json): parse error - <details>
Skip (legacy_v1): duplicate, not registered
✅ Done: 10 registered, 2 skipped
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: strategies.use_db is false. Set use_db: true in forge.yaml |
DB disabled | Update forge.yaml and retry |
No JSON files found for migration. |
strategies.path is empty |
Verify path / file placement |
forge strategy delete¶
Delete a registered strategy from the DB / registry. With --with-results, also deletes related files (optimized strategy, backtest results, optimization results). Journal files are always kept.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
STRATEGY_ID |
argument (required) | - | Strategy ID to delete |
--force |
flag | false | Skip confirmation prompt |
--with-results |
flag | false | Also delete related files (<id>_optimized.json, <id>_report.json, optimize_<id>_*.json) |
Files removed by --with-results¶
strategies.path / <id>_optimized.jsonreport.output_path / <id>_report.jsonreport.output_path / optimize_<id>_*.json(all matching files)
<id>.journal.json is kept.
Automatic cleanup of recommendations.yaml (issue #454)¶
When a strategy is deleted, any matching entry in data/explorer/recommendations.yaml is automatically removed (ranks are renumbered). Deleting an auto-relax recommendation will not leave a stale entry that causes forge explore run to fail with StrategyNotFoundError.
In addition, forge explore recommend show performs a DB existence check at display time and auto-prunes any stale entries left over from previous runs.
Sample output¶
To delete: my_strategy_v1
✓ data/strategies/my_strategy_v1_optimized.json
✓ data/results/my_strategy_v1_report.json
✓ data/results/optimize_my_strategy_v1_20260415_103021.json
- data/journal/my_strategy_v1.journal.json (kept)
Continue? [y/N]: y
✅ Strategy 'my_strategy_v1' deleted
Files deleted: 3
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: strategy '<id>' not found |
Invalid ID | Verify with forge strategy list |
Cancelled |
Declined the prompt | Use --force or re-confirm |
forge strategy purge¶
Purge the strategy JSON, related files (_optimized.json, _report.json, optimize_<id>_*.json), and DB entry in a single command. Replaces the previous three-step rm <strategy>.json && rm <strategy>_report.json && forge strategy delete <id> --force workflow. Journal files (<id>.journal.json) are preserved.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
STRATEGY_ID |
argument (required) | - | Strategy ID to purge completely |
--dry-run |
flag | false | Only list the files that would be deleted; do not actually delete them |
Sample output¶
--dry-run:
[dry-run] Targets:
- data/strategies/my_strategy_v1.json
- data/strategies/my_strategy_v1_optimized.json
- data/results/my_strategy_v1_report.json
- data/results/optimize_my_strategy_v1_20260415_103021.json
- DB entry: my_strategy_v1
Note: data/journal/my_strategy_v1.journal.json is preserved
Normal run:
Targets: my_strategy_v1
✓ data/strategies/my_strategy_v1.json
✓ data/strategies/my_strategy_v1_optimized.json
✓ data/results/my_strategy_v1_report.json
✓ data/results/optimize_my_strategy_v1_20260415_103021.json
- data/journal/my_strategy_v1.journal.json (preserved)
Continue? [y/N]: y
✅ Strategy 'my_strategy_v1' has been purged
Missing files are reported as warnings only and do not abort the command.
Differences vs. delete --with-results¶
| Aspect | delete --with-results |
purge |
|---|---|---|
| Strategy JSON | Kept | Deleted |
<id>_optimized.json |
Deleted | Deleted |
<id>_report.json |
Deleted | Deleted |
optimize_<id>_*.json |
Deleted | Deleted |
| Journal | Preserved | Preserved |
| DB entry | Deleted | Deleted |
Use purge to wipe a strategy completely; use delete --with-results when you want to keep the strategy JSON but clean up related result files.
forge strategy validate¶
Run logical consistency checks on a strategy. With --symbol, also runs dynamic checks (signal counts and condition correlation on real data). Pass a .json path as STRATEGY_ID to validate an unregistered file directly.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
STRATEGY_ID |
argument (required) | - | Strategy ID, or a .json file path |
--symbol |
option | - | Symbol for dynamic checks (enables dynamic phase) |
--period |
option | 1y |
Data period |
--min-signals |
int | 30 |
Min signal count threshold (warning if below) |
--corr-threshold |
float | 0.9 |
Correlation warning threshold |
--json |
flag | false | Output as JSON |
Static checks¶
What StrategyValidator checks when --symbol is not given (implementation-based):
- Required fields:
strategy_id/name/version/timeframe indicators[]: ID uniqueness, reference resolutionentry_conditions/exit_conditions: referenced IDs exist- Condition expressions (
left/op/right): syntax and type integrity risk_management: value ranges (percentages, leverage)regime_config.indicator_idresolves to a real indicatoroptimizer_config.param_rangeskeys map to eitherparametersor an indicatorparams
Dynamic checks (with --symbol)¶
Loads real data and runs a lightweight backtest to gather signal statistics:
- Entry signal count over the period (warns if below
min_signals) - True-day count and percentage of each leaf condition
- Pairwise correlation of conditions (warns above
corr_threshold)
Sample output (text)¶
Strategy: spy_sma_crossover_v1 [OK]
[DYNAMIC CHECKS]
Symbol: SPY Period: 1y Total days: 252
Entry signals: 87 days
Condition True days:
sma_fast > sma_slow: 142 days (56.3%)
rsi < 70: 198 days (78.6%)
✓ No issues detected
When errors are detected:
Strategy: my_v1 [NG]
[ERRORS]
✗ [E_INDICATOR_REF] Reference 'sma_fast' in condition does not exist in indicators
→ Add { "id": "sma_fast", "type": "SMA", ... } to the indicators array
[WARNINGS]
⚠ [W_LOW_SIGNALS] Too few entry signals: 12 days (threshold 30)
→ Loosen the conditions or extend the data period
[CORRELATION]
⚠ rsi < 70 × close > sma_fast: 0.94
→ Drop one of the highly correlated conditions, or replace with an independent one
Sample output (--json)¶
{
"strategy_id": "my_v1",
"ok": false,
"static_errors": [
{"code": "E_INDICATOR_REF", "message": "...", "suggestion": "..."}
],
"static_warnings": [],
"signal_stats": {
"symbol": "SPY",
"period": "1y",
"total_days": 252,
"entry_signal_days": 12,
...
},
"dynamic_warnings": [...],
"correlations": [...]
}
Exit codes¶
result.ok = true→0result.ok = false(errors detected) →1
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: file not found - <path> |
.json path not found |
Verify the path |
forge strategy signals¶
Count entry signals, estimated trades, and WFT window coverage without running optimization or WFT (#321).
| Option | Description | Default |
|---|---|---|
--strategy |
Strategy name (required) | — |
--period |
Historical data period | 5y |
--json |
Output as JSON | off |
Text output example¶
📊 Signal Summary: my_rsi_v1 / SPY (5y)
Long signals: 45 days
Estimated trades: 38
Avg trades/year: 7.6
WFT coverage: low (5-10 trades/window)
JSON output example¶
{
"strategy_id": "my_rsi_v1",
"symbol": "SPY",
"period": "5y",
"total_days": 1260,
"long_signals": 45,
"short_signals": 0,
"estimated_trades": 38,
"avg_per_year": 7.6,
"wft_window_coverage": "low (5-10 trades/window)"
}
| Field | Description |
|---|---|
long_signals |
Number of days with long entry signal |
estimated_trades |
Estimated trades (counted as signal blocks) |
avg_per_year |
Average trades per year |
wft_window_coverage |
Coverage assessment based on trades per WFT window |
Common behavior¶
- Storage: When
config.strategies.use_dbis true, usesSQLiteStrategyRepository; otherwise file-based. Switch viaforge.yaml. - Locations:
config.strategies.path(strategy JSON),config.report.output_path(backtest / optimization results),config.journal.journal_path(journal). - Journal integration: When
config.journal.auto_recordis true,saveautomatically records a journal snapshot. FORGE_CONFIG: All paths above are determined by theforge.yamlreferenced by theFORGE_CONFIGenvironment variable.- Exit codes:
0on success;validatereturns1when errors are detected; argument errors return Click's2; runtime errors typically1.