alpha-forge backtest¶
Run backtests and analyze results. Provides single-strategy runs, parallel batch runs, automated diagnostics, listing/reporting/migrating saved results, multi-strategy comparison, portfolio backtests, dashboard chart navigation, Monte Carlo simulation, and signal count checks.
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 |
|---|---|
alpha-forge backtest run |
Run a backtest for the given symbol and strategy |
alpha-forge backtest batch |
Run parallel backtests for multiple strategy JSON files |
alpha-forge backtest timeframes |
Backtest the same strategy across multiple timeframes and compare |
alpha-forge backtest diagnose |
Automatically diagnose performance issues in a strategy |
alpha-forge backtest list |
Show saved backtest results |
alpha-forge backtest report |
Display a saved backtest result |
alpha-forge backtest migrate |
Import existing JSON report files into the database |
alpha-forge backtest compare |
Compare multiple strategies side by side on the same symbol and period |
alpha-forge backtest combine |
Run a combined portfolio backtest across multiple strategies |
alpha-forge backtest portfolio |
Run a portfolio backtest across multiple symbols |
alpha-forge backtest chart |
Display dashboard URL to navigate to charts |
alpha-forge backtest signal-count |
Fast signal count check without running the full backtest |
alpha-forge backtest monte-carlo |
Run a Monte Carlo simulation from an existing backtest result |
alpha-forge backtest run¶
Run a backtest. Specify either --strategy or --strategy-file.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
SYMBOL |
argument (required) | - | Symbol (e.g. SPY, AAPL, CL=F) |
--strategy |
option | - | Strategy ID (mutually exclusive with --strategy-file) |
--strategy-file |
option | - | Path to a strategy JSON file (no DB registration required) |
--json |
flag | false | Output results as JSON to stdout |
--summary |
flag | false | (With --json) Exclude heavy arrays (trades / monthly_returns / annual_returns / equity_curve) and emit a lightweight JSON of scalar metrics + verdict only (issue #1224, to save tokens for agents / MCP; details) |
--start |
option | - | Start date YYYY-MM-DD |
--end |
option | - | End date YYYY-MM-DD |
--split |
flag | false | Split into in-sample / out-of-sample periods (details) |
--benchmark |
option | config | Benchmark symbol (per-asset_type defaults apply, see below) |
--no-benchmark |
flag | false | Disable benchmark comparison entirely (F-304). Useful for FX / commodities where a SPY comparison is meaningless |
--check-criteria |
flag | false | Run acceptance criteria check |
--cagr-min |
float | 20.0 |
Minimum CAGR (%), used with --check-criteria |
--sharpe-min |
float | None (falls back to goal or 1.0) |
Minimum Sharpe ratio |
--max-dd |
float | None (falls back to goal or 25.0) |
Max drawdown limit (%); also used for pre_filter_pass |
--win-rate-min |
float | 55.0 |
Minimum win rate (%) |
--pf-min |
float | 1.3 |
Minimum profit factor |
--min-trades |
int | - | Minimum trade count; exits with code 1 if below |
--regime |
flag | false | Display per-regime statistics on the console |
--trades-csv |
path | - | Write a per-trade CSV to the given path (issue #800, details) |
--debug |
flag | false | Raise the alpha_forge.* loggers to DEBUG (issue #800) |
--goal |
option | - | Goal name (e.g. default, stocks); auto-loads pre_filter thresholds from goals.yaml into --sharpe-min / --max-dd |
--cost-preset |
option | - | Cost preset name (issue #785, e.g. moomoo-crypto-spot / binance-spot-vip0 / ibkr-us-stock-fixed); overrides the strategy's risk_management commission/slippage in-memory at run time (strategy JSON is untouched) |
--dividend-reinvest |
flag | false | Include dividend-reinvest metrics in the result (#958); requires saved dividends data (data fetch --with-dividends) |
--regime-filter |
option | - | Post-hoc entry gating by macro regime (issue #1012); format <source>:<label> (e.g. macro:risk_on), source=macro only; requires FRED data fetched in advance (data alt fetch FRED:T10Y3M) |
Export per-trade CSV with --trades-csv¶
backtest run --trades-csv <path> writes a one-row-per-trade CSV. Use it to cross-check against another engine (e.g. TradingView Strategy Tester) or to investigate divergences like the profit_factor artifact in issue #791.
| Column | Meaning |
|---|---|
trade_idx |
0-origin trade index |
entry_bar_idx / exit_bar_idx |
Entry / exit bar index |
entry_time / exit_time |
Date (YYYY-MM-DD for 1d timeframes) |
entry_price / exit_price |
Fill price |
direction |
long / short |
pnl_pct / pnl_abs |
Return (%) / absolute PnL |
bars_held |
Holding period in bars |
sl_price / tp_price |
SL / TP price at entry (empty when unset) |
mae_pct / mfe_pct |
Maximum adverse / favorable excursion (%) |
entry_reason |
One-line summary of strategy entry conditions (shared across trades in Phase 1) |
exit_reason |
strategy_exit placeholder (Phase 2 will distinguish SL/TP/trailing) |
Even with zero trades, the header row is still written so that sort / uniq / diff pipelines do not break on empty stdout. --debug raises the alpha_forge.* loggers to DEBUG level, but a backtest that completes normally emits almost no additional DEBUG output (most detailed logs are for diagnosing exceptions). Detailed logs appear on stderr in error paths such as data-fetch failures or signal-evaluation errors.
Benchmark selection logic (F-304)¶
Resolution order when --benchmark is omitted:
- Explicit
--benchmark <SYM>(highest priority) forge.yamlreport.benchmark_symbol, if set to anything other than the defaultSPY- Per-
asset_typemap on the strategy JSON (used when (2) is still defaultSPY)
asset_type |
Default benchmark |
|---|---|
stock / etf |
SPY |
fx |
DX-Y.NYB (Dollar Index) |
crypto |
BTC-USD |
commodity / future |
DBC (commodity ETF) |
| Other / unset | SPY (fallback) |
To disable benchmark comparison entirely, pass --no-benchmark. This is the right choice when alpha / beta / correlation against SPY is meaningless (e.g. FX or commodities strategies).
IS / OOS Split (--split)¶
When --split is specified, the full data range is divided into an In-Sample (IS / training) period and an Out-of-Sample (OOS / validation) period. The IS performance is then independently validated on the OOS period, making it the recommended approach for evaluating strategy generalization.

Progress bar (Rich UI)¶
While running in a TTY, a Rich-powered progress bar is shown on stderr. The backtest progresses through 6 phases below; with --split, both IS and OOS flows run, totaling 12 steps.
| Phase | Description |
|---|---|
指標計算 (Indicators) |
Pre-compute technical indicators |
変数評価 (Variables) |
Evaluate intermediate boolean variables |
シグナル生成 (Signals) |
Evaluate entry/exit conditions and apply risk masks |
シミュレーション (Simulate) |
Run the vectorbt portfolio simulation |
メトリクス算出 (Metrics) |
Compute Sharpe / MDD / win rate, etc. |
レジーム分析 (Regime) |
Compute per-regime metrics (no-op when not configured) |

The progress bar is rendered on stderr, so combining it with --json keeps stdout as pure JSON (when --json is passed and stderr is a TTY, the dashboard is still drawn on stderr). When stderr is not a TTY (CI, pipes, redirected files), the progress bar is automatically suppressed. This way, --json invocations from agent loops like /explore-strategies show progress in interactive terminals without polluting CI logs.
Sample output (text)¶
The leading icon is driven by whether the trade count is statistically sufficient (is_valid = total_trades >= 30): ✅ when sufficient, ⚠️ when not. The signal quality score line always carries a hint about how to read the score (≥0.7 is reliable / 0.4–0.7 caution / <0.4 low reliability).
Running backtest: SPY x sma_crossover_v1
⚠️ Backtest complete Signal quality score: 0.38/1.0 (<0.4: low reliability, treat as reference only)
→ Docs: https://alforgelabs.com/en/docs/cli-reference/backtest/#signal-quality-score
Total Return: +52.30% CAGR: 5.40%
SR: 0.92 Sortino: 1.15 Calmar: 0.32
MDD: -16.80% Duration: 187d Recovery: 92d
PF: 1.74 Win%: 50.0% avg_win: 4.20% avg_loss: -2.40%
Kelly: 0.21 Payoff: 1.75 Expectancy: 0.90%/trade GPR: 0.42 Ulcer: 0.0480 Recovery: 3.11
Trades: 14 Avg hold: 28.5d(28bar) Max: 65.0d(65bar) Win streak: 4 Loss streak: 3
Win rate CI(90%): 35.2% - 64.8%
The Kelly: line shows extended trade-quality metrics: the Kelly criterion (theoretically optimal position fraction), payoff ratio (average win ÷ |average loss|), expectancy (expected return per trade), Gain/Pain ratio, Ulcer Index, and recovery factor (total return ÷ |max drawdown|). With --json these are available as kelly_criterion / payoff_ratio / expectancy_pct / expected_daily_return_pct (and monthly / yearly) / ulcer_index / serenity_index / gain_to_pain_ratio / recovery_factor; values are null when the denominator is undefined (e.g. no losing trades, no drawdown).
Monetary metrics assume USD (issue #1191)
Turnover and cost-related monetary metrics are computed assuming a USD base. No FX conversion is applied for non-USD accounts or symbols, so interpret the reported monetary amounts as USD. Ratio metrics such as return percentage, Sharpe, and win rate are currency-independent.
When the score or trade count fails the recommended thresholds, a warning and a one-line docs link are added (F-302):
⚠️ Backtest complete Signal quality score: 0.43/1.0 (0.4–0.7: caution, more validation suggested)
→ Docs: https://alforgelabs.com/en/docs/cli-reference/backtest/#signal-quality-score
⚠️ Warning: trade count is insufficient (trades=27, minimum 30 recommended)
→ Fewer than 30 trades is statistically noisy and may be filtered out by
optimization / WFT pre_filter. Consider widening the data period (`--start`
to go further back).
→ Docs: https://alforgelabs.com/en/docs/cli-reference/backtest/#signal-quality-score
Signal Quality Score and Minimum Trades (F-302)¶
Signal Quality Score (signal_quality_score, 0.0–1.0)¶
sharpe_score = min(max(sharpe_ratio / 2.0, 0.0), 1.0) # 30%
profit_factor_score = min(max((profit_factor - 1.0) / 1.5, 0.0), 1.0) # 20% (0 when profit_factor is None)
win_rate_score = max(0.0, (win_rate_pct - 50.0) / 30.0) # 20% (only the portion above 50% win rate contributes)
sample_size_score = min(total_trades / 30, 1.0) # 30%
signal_quality_score = (
0.30 * sharpe_score
+ 0.20 * profit_factor_score
+ 0.20 * win_rate_score
+ 0.30 * sample_size_score
)
| Score range | Interpretation | CLI hint |
|---|---|---|
≥ 0.70 |
Reliable | "≥0.7 is reliable" |
0.40 – 0.69 |
Caution. Further validation (WFT / cross-symbol) recommended | "0.4–0.7: caution, more validation suggested" |
< 0.40 |
Low reliability, treat as reference only | "<0.4: low reliability, treat as reference only" |
Detecting all-winning-trades backtest artifacts (issue #791)
When profit_factor is returned as null and StrategyDiagnostics emits an ALL_WINNING_TRADES warning, you are looking at an all-winning-trades backtest artifact. Likely causes include a too-loose trailing stop, exit conditions that almost never fire right after entry, or entry evaluation that has degenerated into a state-based predicate firing every bar. Cross-validate with another engine such as the TradingView Strategy Tester. null is treated as "unmeasurable" by signal_quality_score, anomaly_score, check_criteria.pf, and target_metrics.profit_factor, all of which fall back to the safe side (score = 0, criterion fails).
Why a minimum of 30 trades¶
- Rough threshold for statistical significance: n ≥ 30 (where the Central Limit Theorem starts to apply)
- Below that, the
total_trades < 30flag is raised and thesample_size_scoreterm is linearly penalized - If
total_trades < 10, the result is also marked as "statistically meaningless" withis_valid=false
What happens if not met¶
alpha-forge optimize run --goal <name>/alpha-forge optimize walk-forward --goal <name>will fail thepre_filter.min_tradescheck (default 30), setpre_filter_pass=false, and exclude the strategy from the/explore-strategiesshortlist- A single backtest run is not aborted, but the result is low-confidence — extend the data window (
--startfurther into the past) or rework the indicator mix toward higher signal frequency
Sample output (--json)¶
{
"total_return_pct": 52.30,
"cagr_pct": 5.40,
"sharpe_ratio": 0.92,
"max_drawdown_pct": -16.80,
"win_rate_pct": 50.0,
"profit_factor": 1.74,
"total_trades": 14,
"pre_filter_pass": false,
"pre_filter": { "sharpe_min": 1.0, "max_dd_max": 25.0 },
"next_step": [
"Next: alpha-forge optimize run SPY --strategy sma_crossover_v1 --save",
" or: alpha-forge pine generate --strategy sma_crossover_v1"
],
"warnings": []
}
next_step (issue #1234) returns the recommended next commands as a string array on a successful backtest, so an agent can follow the scaffold → save → backtest → optimize / pine order without inferring it. The text output prints an equivalent guidance line at the end.
Lightweight JSON with --summary (issue #1224)¶
backtest run --json --summary excludes heavy arrays (trades / monthly_returns / annual_returns / equity_curve) and returns a lightweight JSON of scalar metrics + verdict (pre_filter_pass / pre_filter) + next_step only. Use it to cut token usage over agents / MCP. Only the array fields are dropped; the meta-field contract above is unchanged.
See also the top-level field contract in the --json output reference.
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Specify either --strategy or --strategy-file |
Neither given | Provide one of them |
--strategy and --strategy-file are mutually exclusive |
Both given | Use only one |
Error: Invalid start date format (YYYY-MM-DD) |
Date format invalid | Use 2024-01-15 style |
⚠️ {interval} data not found. Falling back to 1d data. |
Strategy timeframe data missing |
Run alpha-forge data fetch for the interval |
alpha-forge backtest batch¶
Run parallel backtests for many strategy JSON files. Strategies passing the filter (Sharpe / MaxDD) are marked as "passed".
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
SYMBOL |
argument (required) | - | Symbol |
--strategy-file |
repeatable | - | Path to a strategy JSON file |
--strategy-dir |
option | - | Directory containing strategy JSON files |
--pattern |
option | *.json |
Glob pattern for --strategy-dir |
--workers |
int | 3 |
Number of parallel workers |
--sharpe-min |
float | 1.0 |
Sharpe lower bound for pre_filter_pass |
--max-dd |
float | 25.0 |
MaxDD upper bound for pre_filter_pass |
--json |
flag | false | Output results as a JSON array to stdout |
Sample output¶
Starting batch backtest: SPY × 5 strategies (workers=3)
✅ spy_sma_v1: Sharpe=1.32 MaxDD=-12.4% CAGR=8.2% trades=18
❌ spy_rsi_v1: Sharpe=0.61 MaxDD=-22.1% CAGR=4.1% trades=24
✅ spy_macd_v1: Sharpe=1.18 MaxDD=-15.6% CAGR=7.0% trades=15
🔴 spy_broken_v1: ERROR - failed to load strategy JSON
Passed strategies: 2/4
✅ spy_sma_v1: Sharpe=1.32 MaxDD=-12.4%
✅ spy_macd_v1: Sharpe=1.18 MaxDD=-15.6%
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Specify either --strategy-file or --strategy-dir |
Neither given | Provide one of them |
🔴 <id>: ERROR - <reason> |
Per-strategy load/run failure | Address the message |
alpha-forge backtest timeframes¶
Backtest the same strategy across multiple timeframes and print a comparison table (timeframe sweep). The strategy's timeframe field is overridden per run, using the stored per-interval data (<SYMBOL>_<interval>.parquet).
Syntax¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
SYMBOL |
Argument (required) | - | Ticker symbol |
--strategy |
One of the two required | - | Saved strategy name |
--strategy-file |
One of the two required | - | Path to a strategy JSON file |
--timeframes |
Option | 1h,4h,1d |
Comma-separated list of timeframes (duplicates are de-duplicated) |
--start / --end |
Option | - | Backtest period (YYYY-MM-DD) |
--json |
Flag | false | Emit results as JSON |
Example output¶
=== Timeframe Comparison: AAPL × sma_crossover_v1 ===
TF Sharpe Return% MDD% PF Win% Trades Bars
──────────────────────────────────────────────────────────────────
1h (no data)
4h 1.12 +38.40 12.10 1.62 54.2 48 4380
1d ★ 1.45 +52.30 16.80 1.74 50.0 14 730
Best: 1d (Sharpe=1.45)
No data: 1h (run `data fetch --interval <tf>` to populate)
When data for an interval is missing, the command does not fall back to 1d data; the row and the trailing summary both report it explicitly. --json returns a {"symbol", "strategy_id", "timeframes": [...], "count", "best_timeframe", "missing_timeframes"} envelope (each entry has timeframe / status / bars / metrics).
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Specify either --strategy or --strategy-file |
Neither or both given | Provide exactly one |
All timeframes report no_data |
Per-interval data not fetched | Run alpha-forge data fetch <SYMBOL> --interval <tf> (exit code 1) |
| Invalid date format | --start/--end not YYYY-MM-DD |
Fix the format (exit code 2) |
alpha-forge backtest diagnose¶
Automatically diagnose performance issues in a strategy (overfitting, low trade count, extreme win/loss balance, etc.).
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
SYMBOL |
argument (required) | - | Symbol |
--strategy |
required | - | Strategy ID |
--start |
option | - | Start date YYYY-MM-DD |
--end |
option | - | End date YYYY-MM-DD |
--split |
flag | true | Split into in-sample / out-of-sample (default ON) |
--json |
flag | false | Output as JSON |
Output¶
The diagnostic result lists the inferred problems and recommended actions. See alpha-forge backtest diagnose --help and the internal StrategyDiagnostics logic for details.
alpha-forge backtest list¶
Show saved backtest results from the DB.
Synopsis¶
Options¶
| Name | Kind | Default | Description |
|---|---|---|---|
--strategy |
option | - | Filter by strategy ID |
--symbol |
option | - | Filter by symbol |
--sort |
option | run_at |
Sort key (sharpe_ratio / total_return_pct / cagr_pct / max_drawdown_pct / win_rate_pct / profit_factor / run_at) |
--limit |
int | 20 |
Number of rows to display |
--best |
flag | false | Show only the best record per group |
--by |
choice | strategy |
Group key for --best (strategy / symbol) |
Sample output¶
Backtest Results (5 records)
run_id strategy_id symbol Return Sharpe MDD Trades
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
spy_sma_v1_20260415_103021 spy_sma_v1 SPY +52.3% 0.92 -16.8% 14
spy_macd_v1_20260414_181522 spy_macd_v1 SPY +38.1% 1.18 -15.6% 12
...
Common messages¶
| Message | Cause |
|---|---|
No saved backtest results found. |
DB empty |
alpha-forge backtest report¶
Display a saved backtest result in detail.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
RESULT_ID |
argument (required) | - | DB mode: strategy_id or run_id. File mode: result_id |
--json |
flag | false | Output the full JSON |
--symbol |
option | - | DB mode: filter by symbol |
If RESULT_ID does not match a run_id, the latest run for that strategy_id is used.
Sample output¶
=== spy_sma_v1 / SPY (2026-04-15T10:30:21) ===
Total Return: 52.30% CAGR: 5.40%
SR: 0.92 Sortino: 1.15 Calmar: 0.32
MDD: -16.80% PF: 1.74 Win%: 50.0%
Trades: 14 Avg hold: 28.5d(28bar) Max: 65.0d(65bar)
Trade log: 14 records (use --json to view all)
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: Result not found - <id> |
Neither run_id nor strategy_id matches |
Verify with alpha-forge backtest list |
alpha-forge backtest migrate¶
Import existing *_report.json files into the DB.
Synopsis¶
Options¶
| Name | Kind | Default | Description |
|---|---|---|---|
--dry-run |
flag | false | Preview without writing to the DB |
--force |
flag | false | Overwrite on run_id conflict |
run_id values are generated as migrated_<file_stem>.
Sample output¶
✅ migrated_spy_sma_v1
♻️ migrated_spy_macd_v1 (overwritten)
Skipping (duplicate): migrated_spy_rsi_v1
Done: 2 imported, 1 skipped
Common messages¶
| Message | Cause |
|---|---|
Report directory does not exist. |
config.report.output_path not created |
No JSON files found to import. |
No *_report.json files |
alpha-forge backtest compare¶
Compare multiple strategies on the same symbol and period.
The two meanings of compare: this one runs new backtests (heavy)
backtest compare runs fresh backtests of the given strategies on the spot and compares them (a heavy operation with side effects). In contrast, journal compare / live compare are read-only and merely reference saved runs / summaries. Despite the shared verb, the cost and side effects differ — be careful not to let an agent generalize "compare is a safe reference" and unintentionally launch long-running backtests.
Synopsis¶
alpha-forge backtest compare <STRATEGY1> [STRATEGY2 ...] --symbol <SYM> [--symbol <SYM> ...] [OPTIONS]
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
STRATEGIES |
arguments (required, repeatable) | - | Strategies to compare (space-separated) |
--symbol / -s |
repeatable (required) | - | Symbols to compare |
--start |
option | - | Start date YYYY-MM-DD |
--end |
option | - | End date YYYY-MM-DD |
--benchmark |
option | config | Benchmark symbol |
--json |
flag | false | Output as JSON |
Sample output (text table)¶
The output uses a one-row-per-metric transposed layout. The first strategy you pass is the "baseline" (基準:); for each subsequent strategy the difference from the baseline is shown in the Delta column (✅ = improvement / ❌ = regression). The final line shows the Winner (most improvements; ties broken by Sharpe Ratio). The header column labels are emitted in Japanese (指標 = metric, 基準: = baseline) regardless of locale.
The period (e.g. (2020-01-01 to present)) is only added to the header when --start / --end are supplied; otherwise only the strategy count is shown.
=== Strategy Comparison: SPY (2 strategies) ===
────────────────────────────────────────────────────────────
指標 基準: sma_crossover_v1 sma_cross_qs Delta
────────────────────────────────────────────────────────────
Sharpe Ratio 0.92 1.18 +0.26 ✅
Total Return % 52.30% 38.10% -14.2% ❌
CAGR % 5.40% 4.20% -1.2% ❌
Max Drawdown % -16.80% -15.60% +1.2% ✅
Win Rate % 50.00% 58.00% +8.0% ✅
Profit Factor 1.74 1.92 +0.18 ✅
────────────────────────────────────────────────────────────
Winner: sma_cross_qs (4/6 metrics)
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: Data for <SYM> not found. |
Data missing | alpha-forge data fetch <SYM> |
Warning: Failed to load strategy '<id>' |
Invalid strategy ID / JSON | alpha-forge strategy list, alpha-forge strategy validate |
alpha-forge backtest combine¶
Run a combined portfolio backtest across two or more strategies. Phase 1 supports equal allocation; Phase 2 supports custom weights.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
STRATEGY_IDS |
arguments (required, 2 or more) | - | Strategy IDs to combine (e.g. iwm_sma200_bho_v1 qqq_ema_macd_v2) |
--allocation |
choice | equal |
Allocation method (equal / custom) |
--weights |
option | - | Weights when --allocation custom, e.g. sid1=0.4,sid2=0.6. Sum must be within 1.0 ± 0.01. Required with --allocation custom |
--wft |
int | - | Number of windows for a walk-forward test (>= 2). Returns a wft section when set |
--dividend-reinvest |
flag | false | Include dividend-reinvest metrics. Loads saved dividend data for each strategy's symbol and reflects it in the combined result |
--json |
flag | false | Output results as JSON to stdout |
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: --allocation custom requires --weights |
--allocation custom given without --weights |
Provide --weights |
Error: invalid --weights token (expected sid=val): <token> |
Malformed --weights entry |
Use sid1=0.4,sid2=0.6 style |
alpha-forge backtest portfolio¶
Run a portfolio backtest across multiple symbols.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
SYMBOLS |
arguments (required, repeatable) | - | Space-separated list of symbols |
--strategy |
required | - | Strategy ID |
--allocation |
choice | equal |
Allocation method (equal / risk_parity / custom) |
--weights |
option | - | Custom weights AAPL=0.4,MSFT=0.6 (used with --allocation custom) |
--json |
flag | false | Output as JSON |
--save |
flag | false | Save results to a file |
Sample output¶
Running portfolio backtest: ['AAPL', 'MSFT', 'GOOGL'] (equal allocation)
=== Portfolio Results: tech_basket_v1 (equal) ===
Symbols: AAPL, MSFT, GOOGL
Weights: AAPL=33.3%, MSFT=33.3%, GOOGL=33.3%
Total Return: 78.40% CAGR: 12.30%
SR: 1.45 Sortino: 1.85 Calmar: 0.62
MDD: -19.80% CVaR(95%): -3.20%
Diversification ratio: 1.085
Symbol Weight Return SR MDD
──────────────────────────────────────────────────
AAPL 33.3% +85.2% 1.52 -22.1%
MSFT 33.3% +72.0% 1.41 -18.4%
GOOGL 33.3% +78.0% 1.38 -19.5%
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: Invalid --weights format. |
Format violation | Use AAPL=0.4,MSFT=0.6 style |
Error: Data for <SYM> not found. |
Data missing | alpha-forge data fetch |
Error: Backtest failed - <reason> |
Engine exception | Address the message |
alpha-forge backtest chart¶
Show the dashboard chart URL and optionally open it.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
RESULT_ID |
argument (optional) | - | run_id or strategy_id |
--open |
flag | false | Open the URL in a browser |
--compare |
repeatable | - | Strategy to compare. Use strategy_id:run_id to target a specific run |
Sample output¶
?run_id= contains the RESULT_ID you passed verbatim (pass a strategy_id and you get that strategy_id; pass a specific run_id and you get that run_id).
When comparing strategies:
The command itself only prints a URL. To view charts, start alpha-vis serve (alpha-visualizer).
alpha-forge backtest signal-count¶
Skip vectorbt and just count entry-condition signal occurrences. Useful for sanity-checking condition expressions.
Synopsis¶
alpha-forge backtest signal-count <SYMBOL> --strategy <ID> [--period 5y] [--estimate-trades] [--json]
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
SYMBOL |
argument (required) | - | Symbol |
--strategy |
required | - | Strategy ID |
--period |
option | 5y |
Data period (e.g. 5y, 1y, 6m, 30d) |
--estimate-trades |
flag | false | Estimate expected trades from signal blocks (for trend-following strategies) |
--json |
flag | false | Output as JSON |
Sample output¶
Signal count check: spy_sma_v1 × SPY (5y)
Total bars: 1258 days
Entry conditions (long AND):
sma_fast > sma_slow : 687 days ( 54.6%)
──────────────────────────────────
All conditions AND : 687 days ( 54.6%)
Signal days per regime:
state=0 : 312 days ( 24.8%)
state=1 : 375 days ( 29.8%)
If 0 signals, ⚠️ No signals generated is printed.
Strategies that reference external symbols
For strategies that reference external symbols (e.g. ^VIX, USDJPY=X), signal-count now applies the same merge_external_symbols() step used by alpha-forge backtest run / alpha-forge optimize run before counting signals. The bug (#266) where entry_signal_days would always be 0 for external-symbol strategies has been fixed.
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Invalid period format: <value> |
Bad --period |
Use 5y, 6m, 30d style |
Error: No data for <SYM> (period: <p>). |
Data exists but no rows fall within the period | Widen --period, or re-fetch with alpha-forge data fetch <SYM> |
⚠️ 1d data not found. Falling back to 1d data. + ❌ データが見つかりません: <SYM> (1d) + a data-fetch hint |
The parquet file itself was never fetched | alpha-forge data fetch <SYM> |
alpha-forge backtest monte-carlo¶
Resample trade history from a saved backtest result and run a Monte Carlo simulation to evaluate ruin probability and worst-case scenarios.
Synopsis¶
Arguments and options¶
| Name | Kind | Default | Description |
|---|---|---|---|
RESULT_ID |
argument (required) | - | run_id or strategy_id |
--simulations |
int | 1000 |
Number of simulation runs |
--json |
flag | false | Output as JSON |
Sample output¶
Running Monte Carlo simulation: spy_sma_v1_20260415_103021 (1000 runs)
✅ Simulation complete
Initial capital: 10000
Mean final equity: 14820
Median final equity: 14250
Worst final equity: 7340
Best final equity: 23150
Mean max MDD: 18.40%
95% max MDD: 31.20%
Ruin probability: 0.40%
Sample output (--json)¶
With --json, in addition to the statistics, a default verdict (pre_filter_pass / pre_filter) is always included even without --goal, with naming and contract aligned with backtest run / optimize walk-forward (issue #1237). The verdict checks whether the ruin probability (ruin_probability_pct) is at or below the default threshold of 5.0%.
{
"initial_capital": 10000,
"simulations_run": 1000,
"mean_final_equity": 14820,
"worst_final_equity": 7340,
"best_final_equity": 23150,
"mean_max_drawdown_pct": 18.40,
"max_drawdown_95pct": 31.20,
"ruin_probability_pct": 0.40,
"pre_filter_pass": true,
"pre_filter": { "max_ruin_pct": 5.0 }
}
See also the top-level field contract in the --json output reference.
Common errors¶
| Message | Cause | Fix |
|---|---|---|
Error: Result not found - <id> |
Not in DB | Check alpha-forge backtest list |
Error: No valid trade history found (minimum 10 trades required). |
Trade count < 10 | Use a longer period or a different strategy |
Error: Simulation failed - <reason> |
Exception during simulation | Address the message |
Common behavior¶
- DB / file mode:
list,report,migrate,monte-carlouseconfig.report.output_path / config.report.db_filename(SQLite) as the primary store. FORGE_CONFIG: The strategy / data / results locations are determined by theforge.yamlreferenced by theFORGE_CONFIGenvironment variable.- Exit codes:
0on success,1when--min-tradesis below threshold,click.UsageErrorfor argument errors, fatal errors print to stderr and exit. - Trial plan limit: On the Trial plan, the maximum input data date is capped at
2023-12-31. See Trial Limits for details.