Skip to content

Alpha Force Repo Enhancement Suggestion

Source: Notion | Last edited: 2025-12-16 | ID: 2ca2d2dc-3ef...


  1. 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):**
```py
except Exception as e:
click.echo(f"✗ Mode comparison failed: {str(e)}", err=True)
import traceback
if not quiet:
traceback.print_exc()
sys.exit(1)
  • 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.
  1. 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)
  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
  1. In library code (non-CLI boundary) prefer allowing unexpected exceptions to propagate; convert only well-understood exceptions into domain errors.

  2. 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.

  • 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.

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 boundary
1. 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):**
```py
if not inputs:
raise ValueError("inputs cannot be empty")
  • 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.

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)}")
  • 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.

Before:

# plugin body assumes well-formed inputs
for inp in inputs:
w = inp['weight'] # KeyError if missing, far from entry

After:

# validate first
for i, inp in enumerate(inputs):
if 'weight' not in inp:
raise ValueError(f"inputs[{i}] missing required key: weight")
# then compute
1. 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):**
```py
warmup_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_VALUE
delay = BASE_RETRY_DELAY_SEC * (2 ** attempt) + (0.1 * attempt) # Why 0.1? Should be LINEAR_BACKOFF_FACTOR
  • 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.

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:
```py
parameters = {
"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:
  • 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.
  • During manifest generation (manifest_generator), introspect function signature using inspect.signature and 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.
  • 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)