Skip to content

fix(models): Kelly sizing returns None when historical data is absent (#17)#30

Open
bradsmithmba wants to merge 1 commit into
cloudtrainerwork:masterfrom
bradsmithmba:fix/kelly-insufficient-data
Open

fix(models): Kelly sizing returns None when historical data is absent (#17)#30
bradsmithmba wants to merge 1 commit into
cloudtrainerwork:masterfrom
bradsmithmba:fix/kelly-insufficient-data

Conversation

@bradsmithmba

Copy link
Copy Markdown

Summary

ScoringEngine._calculate_kelly_sizing() was called with call-site defaults when a strategy had no historical performance data:

kelly_size = self._calculate_kelly_sizing(
    metrics.get('win_rate', 0.5),
    metrics.get('avg_win', 1.0),
    metrics.get('avg_loss', 1.0)
)

With those defaults, Kelly = (0.5*1 - 0.5) / 1 = 0. Every strategy lacking backtest data was sized at exactly 0 — indistinguishable from a genuine "no edge" result (Kelly is also 0 for a true 50/50, 1:1 bet) — and surfaced to the user as "Recommended position size: 0%", which reads as advice rather than missing data.

Closes #17.

Fix

_calculate_kelly_sizing(metrics) now takes the metrics dict and returns Optional[float]:

  • None when win_rate, avg_win, avg_loss are not all present (no statistical basis to size).
  • 0.0 when the data is present but there is genuinely no edge.
  • a positive fraction otherwise (unchanged behavior).

ScoredStrategy.kelly_size becomes Optional[float]. recommendation_engine renders None as "insufficient historical data to size" instead of formatting it as 0.0%.

Tests

$ pytest tests/models/test_scoring_engine.py -q
11 passed

Two new tests pin the distinction:

  • test_kelly_sizing_none_without_historical_data — missing inputs → None
  • test_kelly_sizing_zero_for_no_edge — present inputs, no edge → 0.0

CI note

src/models/recommendation_engine.py currently cannot be imported because it imports the missing src.models.integrated_selector (#21), so its None-rendering branch can't be exercised in CI yet. The change there is a defensive guard for when #21 lands; the scoring-engine behavior and its tests run independently and pass now.

🤖 Generated with Claude Code

_calculate_kelly_sizing received win_rate/avg_win/avg_loss with call-site
defaults of 0.5/1.0/1.0 when a strategy had no historical data. Those
defaults yield Kelly = (0.5*1 - 0.5)/1 = 0, so every strategy without
backtest data was sized at 0 — indistinguishable from a genuine "no edge"
(Kelly = 0) result and presented to the user as "position size: 0%".

Take the metrics dict directly and return None when win_rate, avg_win, and
avg_loss are not all present. ScoredStrategy.kelly_size becomes
Optional[float]; recommendation_engine renders None as "insufficient
historical data to size" instead of formatting it as 0.0%.

A present-but-no-edge case still returns 0.0, so callers can distinguish
"no data" (None) from "no edge" (0.0). Two tests cover both.

Closes cloudtrainerwork#17

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bradsmithmba

Copy link
Copy Markdown
Author

Dependency note: the scoring-engine tests in this PR pass on their own (scoring_engine only imports strategies.base). The one piece that can't be exercised in CI yet is the recommendation_engine None-rendering branch — src/models/recommendation_engine.py imports the missing src.models.integrated_selector (#21), so that module is unimportable until #21 is resolved. That edit is a defensive guard for when #21 lands; nothing here depends on #1/PR #19.

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.

Kelly criterion sizing silently returns 0 when no historical performance data is available

1 participant