Alpha Force Repo Enhancement Suggestion
Source: Notion | Last edited: 2025-12-16 | ID: 2ca2d2dc-3ef...
- PatternName: Inconsistent Error Handling — Bare Exception Catches
# PatternName: Inconsistent Error Handling — Bare Exception Catches
**Type:** CodeSmell
## Where (examples)- `packages/alpha-forge-core/alpha_forge/cli/run.py`: `_run_compare_modes` (multiple occurrences)- `packages/alpha-forge-core/alpha_forge/api.py`: `run_strategy`- `packages/alpha-forge-core/alpha_forge/orchestrator/runner.py`: `run_dag` # ❌ Re-raises but loses context- `scripts/fetch_market_cap_historical.py`: `parse_market_cap`, `parse_price`, `CMCScraperWithRestart`
## What (example snippet)
**Now (problematic):**
```pyexcept Exception as e: click.echo(f"✗ Mode comparison failed: {str(e)}", err=True) import traceback if not quiet: traceback.print_exc() sys.exit(1)Why it’s wrong (principles violated)
Section titled “Why it’s wrong (principles violated)”- Catches virtually all exceptions (including SystemExit, KeyboardInterrupt, MemoryError), violating fail-fast and abort semantics.
- Hides root cause and stack trace from normal program flow, making debugging and CI failure triage hard.
- Converts unexpected internal errors into generic user-facing messages without structured typing that callers/agents can handle.
How to fix (concrete guidance)
Section titled “How to fix (concrete guidance)”- Catch only expected exceptions where the code can meaningfully recover or translate them to a domain-specific error. Example:
except (ValueError, AssertionError) as exc: click.echo(f"✗ Mode comparison failed: {exc}", err=True) if not quiet: import traceback; traceback.print_exc() sys.exit(1)- Introduce a small domain exception hierarchy (new module:
alpha_forge/domain/exceptions.py):
class AlphaForgeError(Exception): "Base for domain errors"
class PluginExecutionError(AlphaForgeError): pass
class ValidationError(AlphaForgeError): pass
class WarehouseConnectionError(PluginExecutionError): pass-
In library code (non-CLI boundary) prefer allowing unexpected exceptions to propagate; convert only well-understood exceptions into domain errors.
-
Reserve
except Exception:only at top-level CLI entrypoint to format a friendly message for end users, but always log the full traceback (logging.exception) before exit.
Why the fix is better
Section titled “Why the fix is better”- Preserves original exception types and stacks for CI, logs, and debugging while giving user-friendly messages when appropriate.
- Enables programmatic handling (agents or callers can inspect exception types) and targeted unit tests.
- Improves reliability and reduces silent failures.
Examples — before/after
Section titled “Examples — before/after”Before:
try: result = run_strategy(...)except Exception as exc: return {"status": "error", "error": str(exc)}After:
try: result = run_strategy(...)except (ValueError, RuntimeError) as exc: return {"status": "error", "error": str(exc), "type": exc.__class__.__name__}# unexpected exceptions bubble up and are handled by CLI boundary1. PatternName: Missing Input Validation on Plugin Parameters
```markdown# PatternName: Missing Input Validation on Plugin Parameters
**Type:** CodeSmell
## Where (examples)- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/losses/unified_profit.py:30`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/losses/profit_tx_hold_cost.py:30-130`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/losses/profit_position_cost.py:1-110`- `packages/alpha-forge-shared/alpha_forge_caps/shared/plugins/data/pypi_gapless_crypto_data_binance_spot.py:121`- `packages/alpha-forge-shared/alpha_forge_caps/shared/plugins/data/sample_csv.py:85`- `packages/alpha-forge-shared/alpha_forge_caps/shared/plugins/signals/cs_rank.py:85`- `packages/alpha-forge-shared/alpha_forge_caps/shared/plugins/signals/linear_combo.py:43-115`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/momentum.py:73-96`- `packages/alpha-forge-shared/alpha_forge_caps/shared/plugins/signals/linear_combo.py`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/losses/financial.py:43-96` No validation that predictions and targets- (Search: plugin decorators declare parameters but runtime validation missing)
## What (example snippet)
**Now (problematic):**
```pyif not inputs: raise ValueError("inputs cannot be empty")Why it’s wrong (principles violated)
Section titled “Why it’s wrong (principles violated)”- Parameters are not validated at plugin entry; invalid shapes/values surface deep inside computation and raise cryptic KeyError/TypeError from pandas/numpy.
- Violates principle: “Validate early, fail fast” — callers should get a clear, local error describing which argument is invalid.
- Makes debugging and agent-driven remediation hard because errors don’t mention the offending parameter or index.
How to fix (concrete guidance)
Section titled “How to fix (concrete guidance)”Validate all plugin parameters at function entry. For structured inputs validate each element with explicit messages:
if not isinstance(inputs, list): raise TypeError(f"inputs must be a list, got {type(inputs).__name__}")for i, inp in enumerate(inputs): if not isinstance(inp, dict): raise TypeError(f"inputs[{i}] must be dict, got {type(inp).__name__}") if 'col' not in inp: raise ValueError(f"inputs[{i}] missing required key: 'col'") if 'weight' not in inp: raise ValueError(f"inputs[{i}] missing required key: 'weight'")For numeric scalar params validate ranges and types early:
if not isinstance(window, int) or window <= 0: raise ValueError(f"window must be a positive integer, got {window!r}")For dataframe inputs validate required columns before processing:
required = {"ts", "symbol", "close"}missing = required - set(df.columns)if missing: raise ValueError(f"DataFrame missing required columns: {sorted(missing)}")Why the fix is better
Section titled “Why the fix is better”- Errors are raised close to the source with precise messages, making them actionable for users and agents.
- Avoids deep-stack cryptic failures and reduces debugging time.
- Enables unit tests that assert specific validation errors for malformed inputs.
Example — before/after
Section titled “Example — before/after”Before:
# plugin body assumes well-formed inputsfor inp in inputs: w = inp['weight'] # KeyError if missing, far from entryAfter:
# validate firstfor i, inp in enumerate(inputs): if 'weight' not in inp: raise ValueError(f"inputs[{i}] missing required key: weight")# then compute1. PatternName: Hardcoded Magic Numbers Without Constants
```markdown# PatternName: Hardcoded Magic Numbers Without Constants
**Type:** CodeSmell
## Where (examples)- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/momentum.py:36` (`warmup_formula="window + 1"` - unclear why +1)- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/momentum.py:153` warmup_formula="max(fast, slow) * 5" # Why 5? Should be MACD_CONVERGENCE_MULTIPLIER- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/losses/financial.py:363`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/data/fetchers/binance_funding.py:383`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/cross_sectional/normalization.py:107`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/quality_score.py:119`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/losses/financial.py:363` weights = weights / (weights.sum() + 1e-8) # Why 1e-8? Should be EPSILON_WEIGHT_NORMALIZATION- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/backtest/bar_level.py:658` sharpe = float(pnl.mean() / (pnl.std() + 1e-12) * np.sqrt(252)) # Why 1e-12? Should be EPSILON_STD_AVOID_ZERO- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/oscillators.py:28` warmup_formula="window * 5" # Why 5? Should be RSI_CONVERGENCE_MULTIPLIER- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/moving_averages.py:100` warmup_formula="window * 3" # Why 3? Should be EMA_CONVERGENCE_MULTIPLIER- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/quality_score.py:153` mdd_normalized = (drawdown_clipped + 0.5) * 2 # Why 0.5 and 2? Should be MDD_SCALE_OFFSET, MDD_SCALE_MULTIPLIER- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/oscillators.py:101,110` rsi_values = (rsi_values / 100.0) - 0.5 # Why 100.0 and 0.5? Should be RSI_MAX_VALUE, RSI_CENTER_OFFSET- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/oscillators.py:188` cci_values = cci_values / 200.0 # Why 200.0? Should be CCI_NORMALIZATION_FACTOR- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/oscillators.py:257,482,483` stoch_values = (stoch_values / 100.0) - 0.5 # Why 100.0 and 0.5? Should be STOCH_MAX_VALUE, STOCH_CENTER_OFFSET- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/backtest/bar_level.py:621,622,645,651,653` fixed_cost = (fee_bps / 10000.0) * date_turnover # Why 10000.0? Should be BPS_TO_DECIMAL## What (example snippet)
**Now (problematic):**
```pywarmup_formula = "window + 1"weights = weights / (weights.sum() + 1e-8)delay = BASE_RETRY_DELAY_SEC * (2 ** attempt) + (0.1 * attempt)lambda x: (x.rank() - 1) / (len(x) - 1) if len(x) > 1 else 0.5 # Why 0.5? Should be DEFAULT_RANK_VALUEdelay = BASE_RETRY_DELAY_SEC * (2 ** attempt) + (0.1 * attempt) # Why 0.1? Should be LINEAR_BACKOFF_FACTORWhy it’s wrong (principles violated)
Section titled “Why it’s wrong (principles violated)”- Magic numbers without context obscure intent and reasoning; future maintainers cannot tell whether a value is empirical, domain-driven, or arbitrary.
- Small changes to these numbers can have large behavioral impacts; without documented rationale it’s risky to tweak them.
- Violates the principle: “make implicit intent explicit” — constants should document units, provenance, and acceptable ranges.
How to fix (concrete guidance)
Section titled “How to fix (concrete guidance)”Replace inline numbers with named constants and add explanatory comments:
Warmup formula: window + 1 to account for first-difference consuming one row
Section titled “Warmup formula: window + 1 to account for first-difference consuming one row”WARMUP_FORMULA = “window + 1” # +1 compensates for diff operation; prevents zero-length warmup
- When a number is empirical, say so and, if possible, link to an ADR, issue, or test that documents experiments.- For formulas, prefer expressing as code (function) with a short docstring instead of an unexplained string.
## Why the fix is better
- New readers immediately understand units and intent and can make informed adjustments.- Tests and ADRs can reference the named constants; changing a constant becomes a deliberate, documented decision.- Facilitates safer refactors and reduces accidental regressions.1. PatternName: Inconsistent Parameter Naming in Decorators vs Functions
```markdown# PatternName: Inconsistent Parameter Naming in Decorators vs Functions
**Type:** CodeSmell
## Where (examples)- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/momentum.py`: decorator metadata (lines ~49-56) vs function signature mismatch- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/price_levels.py:85,194`- `packages/alpha-forge-middlefreq/alpha_forge_caps/middlefreq/plugins/features/traditional/bands.py:111-112`- General: any plugin using `@register_plugin` / decorator-based metadata where declared parameters differ from runtime signature
## What (example snippet)
**Now (problematic):**
Decorator declares:
```pyparameters = { "window": { "type": "int", "default": 20, "description": "Lookback period in bars for momentum calculation", "valid_range": [2, 1000], },}def momentum( series: pd.Series, *, window: int = 20, output_cols: List[str], output_types: List[str], **_: Any,) -> pd.DataFrame:Why it’s wrong (principles violated)
Section titled “Why it’s wrong (principles violated)”- The decorator is used as the manifest/source-of-truth for DSL; mismatches break the contract between DSL authors and runtime plugin behavior.
- DSL authors receive misleading manifests; runtime will accept unexpected args or ignore declared ones, leading to cryptic failures.
- No automated check exists during manifest generation to verify that decorator-declared parameter names align with function parameter names.
How to fix (concrete guidance)
Section titled “How to fix (concrete guidance)”- During manifest generation (
manifest_generator), introspect function signature usinginspect.signatureand compare parameter names to decorator-declared params. - Exclude well-known reserved params (
output_cols,output_types,**kwargs) from strict checks. - If mismatch found, fail manifest generation with actionable message:
"Function declares parameter 'output_cols' but decorator missing parameter 'output_cols'. Update decorator or function to match." - Optionally provide autofix suggestions: rename decorator key to match function param or vice versa in the manifest generator output.
Why the fix is better
Section titled “Why the fix is better”- Ensures manifest and implementation stay in sync; DSL documentation will be accurate and reliable for users and agents.
- Catches parameter contract violations at build/manifest time rather than at runtime deep in execution.
- Improves DX and reduces time debugging parameter name mismatches.
1. (Upcomming)