Skip to content

fix(features): deterministic, symbol-aware IV estimate (#9)#31

Open
bradsmithmba wants to merge 1 commit into
cloudtrainerwork:masterfrom
bradsmithmba:fix/deterministic-iv
Open

fix(features): deterministic, symbol-aware IV estimate (#9)#31
bradsmithmba wants to merge 1 commit into
cloudtrainerwork:masterfrom
bradsmithmba:fix/deterministic-iv

Conversation

@bradsmithmba

Copy link
Copy Markdown

Summary

ImpliedVolatilityEstimator had two compounding defects in src/features/greeks.py:

  1. Non-deterministic. estimate_iv() multiplied a per-category base volatility by a random factor seeded on wall-clock time:

    random.seed(int(time.time()) % 100)
    adjustment = random.uniform(0.8, 1.2)
    return base_vol * adjustment

    The same symbol returned different IVs on consecutive calls, so no Greek or probability-of-profit derived from it was reproducible across a run.

  2. SPY hardcoded. get_iv_for_position() called estimate_iv("SPY") unconditionally, applying a low-vol ETF base (0.15) to every position regardless of the real underlying — systematically underestimating IV for individual names.

Closes #9.

Fix

  • Remove the time-seeded random factor; estimate_iv() returns the deterministic per-category base (ETF 0.15 / large-cap tech 0.25 / general 0.30).
  • Add an optional symbol argument to get_iv_for_position(position, symbol=None). When the underlying is known it flows through; when unknown, fall back to the general single-stock base (0.30) instead of assuming SPY. (Position carries no symbol field today, which is why the method had nothing to pass — the new parameter lets callers supply it.)

Verification

Exercised directly (the test file can't be collected on master yet — see below):

estimate_iv('AAPL') x2 -> 0.25, 0.25   (deterministic)
estimate_iv('SPY')     -> 0.15
estimate_iv('XYZ')     -> 0.30
position IV, no symbol -> 0.30 base (was 0.15 SPY)
position IV, 'AAPL'    -> 0.25 base   (symbol now matters)

A determinism test (test_iv_estimation_is_deterministic) is added to tests/test_greeks.py.

CI note

tests/test_greeks.py imports src.features.greeks, which triggers src/features/__init__.py → the data layer missing until #1 (PR #19) merges, so the file can't be collected on master yet. The fix was verified by loading the module directly (it depends only on position_models, numpy, and scipy). The new test runs in CI once #1 lands.

🤖 Generated with Claude Code

ImpliedVolatilityEstimator.estimate_iv() multiplied a per-category base
volatility by random.uniform(0.8, 1.2) seeded on int(time.time()) % 100,
so the same symbol returned different IVs on consecutive calls and no Greek
or POP derived from it was reproducible. get_iv_for_position() also called
estimate_iv("SPY") unconditionally, applying a low-vol ETF base (0.15) to
every position regardless of the real underlying.

- Remove the wall-clock-seeded random factor; return the deterministic
  per-category base (ETF 0.15 / large-cap tech 0.25 / general 0.30).
- Add an optional `symbol` argument to get_iv_for_position so the real
  underlying flows through; when it is unknown, fall back to the general
  single-stock base rather than assuming SPY.

Verified: estimate_iv('AAPL') == estimate_iv('AAPL') == 0.25; position IV
now differs by symbol. Adds a determinism test.

Closes cloudtrainerwork#9

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

Copy link
Copy Markdown
Author

Merge-ordering note: the determinism test added here lives in tests/test_greeks.py, which imports src.features.greekssrc/features/__init__.py → the data layer missing until #1 (PR #19) merges, so the file can't be collected on master yet. Recommend merging #19 first; after that the test runs and passes. The fix was verified by loading the module directly (it depends only on position_models, numpy, scipy).

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.

ImpliedVolatilityEstimator is non-deterministic (time-seeded random) and always uses SPY as base regardless of underlying

1 participant