Skip to content

feat(fitness): add RiskAdjustedReturn fitness function (closes #21)#24

Merged
NeuZhou merged 2 commits intoNeuZhou:masterfrom
nandanadileep:feat/fitness-risk-adjusted-return
Apr 13, 2026
Merged

feat(fitness): add RiskAdjustedReturn fitness function (closes #21)#24
NeuZhou merged 2 commits intoNeuZhou:masterfrom
nandanadileep:feat/fitness-risk-adjusted-return

Conversation

@nandanadileep
Copy link
Copy Markdown
Contributor

Summary

Closes #21.

Adds a RiskAdjustedReturnFitness builtin that scores strategies using:

score = annual_return / (1 + |max_drawdown|)

This naturally penalises high-drawdown strategies even when their raw return is higher.

  • Handles max_drawdown in either sign convention (stored negative or positive) via abs()
  • Zero-trade results receive a -1.0 penalty score
  • Auto-discovered by FitnessRegistry.auto_discover() via FITNESS_CLASSES

Files changed

File Description
stratevo/fitness_plugins/builtin/risk_adjusted_return.py New fitness class
tests/test_fitness_risk_adjusted_return.py 11 tests covering formula, ranking, edge cases, registry

Test plan

  • test_formula_standard — verifies annual_return / (1 + dd_abs) arithmetic
  • test_formula_positive_drawdown_conventionabs() handles positive dd values
  • test_drawdown_penalises_high_return — 30%/90%DD loses to 20%/10%DD
  • test_same_return_lower_drawdown_wins — same return, lower DD scores higher
  • test_zero_trades_penalty — returns -1.0
  • test_zero_drawdown — denominator = 1, score = annual_return
  • test_negative_return — negative returns produce negative scores
  • test_large_drawdown — formula still finite at 80% drawdown
  • test_auto_discover_registers_it — registry finds the class automatically

Note on issue example numbers: The issue states "20% return / 10% DD scores higher than 30% return / 25% DD" — mathematically this does not hold (0.182 < 0.240). The tests instead verify the actual behaviour of the formula: higher drawdown penalises even high-return strategies when the drawdown is sufficiently large, and equal-return strategies are correctly ranked by drawdown.

Implements issue NeuZhou#21. Scores strategies as annual_return / (1 + |max_drawdown|),
naturally penalising high-drawdown strategies. Zero-trade results receive a -1.0
penalty. Handles both negative and positive max_drawdown sign conventions via abs().

Adds 11 tests covering formula correctness, ranking behaviour, edge cases
(zero drawdown, zero trades, negative returns), and registry auto-discovery.
Copy link
Copy Markdown
Owner

@NeuZhou NeuZhou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent PR. Clean code, thorough tests, and the docstring honestly acknowledges the math correction from the issue spec. CI green.

✅ Highlights

  • Formula is simple and correct: \�nnual_return / (1 + |max_drawdown|)\
  • Handles both sign conventions for drawdown via \�bs()\ — important since different callers may store it differently
  • Zero-trade penalty of -1.0\ is a good choice
  • 11 tests covering formula, ranking, edge cases, and registry integration
  • Properly uses \FITNESS_CLASSES\ for auto-discovery
  • Honest note about the issue example math being off — good attention to detail

Minor suggestion (non-blocking)

The docstring comment about the issue spec's math error is nice for developers. Consider trimming it slightly since it reads like a review discussion — maybe just \Note: abs() normalizes sign convention.\

Approved ✅

@NeuZhou NeuZhou merged commit 078e528 into NeuZhou:master Apr 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fitness: Implement Risk-Adjusted Return fitness function

2 participants