Skip to content

Commit 88ac7fe

Browse files
JohnCCarterclaude
andcommitted
fix(research): honor configured fib scale_mode in level-event detection (PR #33 P2)
Repo configs default fib.scale_mode to "log", but run_level_events, _aggregate_leg_events and collect_candidates called detect_level_events (and the row-price helper) without scale_mode, so they scanned/reported LINEAR levels — different prices than the log-scale protocol and charts. Thread settings.fib.scale_mode through all three call sites and _level_rows_from_swing. Adds a regression test asserting the level ladder is log. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c1915c0 commit 88ac7fe

3 files changed

Lines changed: 57 additions & 6 deletions

File tree

src/fibengine/research/human_review_rows.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ def _relation_for_bar(df: pd.DataFrame, bar_idx: int, price: float) -> str:
7575
)
7676

7777

78-
def _level_rows_from_swing(swing: Swing, ratios: list[float]) -> list[dict]:
79-
prices = fib_levels(swing, ratios)
78+
def _level_rows_from_swing(
79+
swing: Swing, ratios: list[float], scale_mode: str = "linear"
80+
) -> list[dict]:
81+
prices = fib_levels(swing, ratios, scale_mode=scale_mode)
8082
return [
8183
{"ratio": f"{ratio:g}", "price": round(float(prices[ratio]), 6)} for ratio in sorted(prices)
8284
]
@@ -222,8 +224,15 @@ def collect_candidates(
222224
else:
223225
lo = swing.end.index
224226
hi = n
225-
streams = detect_level_events(df, swing, level_cfg, ratios, settings.pivots.atr_period)
226-
level_rows = _level_rows_from_swing(swing, ratios)
227+
streams = detect_level_events(
228+
df,
229+
swing,
230+
level_cfg,
231+
ratios,
232+
settings.pivots.atr_period,
233+
scale_mode=settings.fib.scale_mode,
234+
)
235+
level_rows = _level_rows_from_swing(swing, ratios, settings.fib.scale_mode)
227236
for stream in streams:
228237
for ev in stream.events:
229238
if lo <= ev.bar_index < hi:

src/fibengine/research/level_events.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,12 @@ def run_level_events(
233233
streams: list[LevelInteractionStream] = []
234234
if swing is not None:
235235
streams = detect_level_events(
236-
df, swing, level_cfg, settings.fib.levels, settings.pivots.atr_period
236+
df,
237+
swing,
238+
level_cfg,
239+
settings.fib.levels,
240+
settings.pivots.atr_period,
241+
scale_mode=settings.fib.scale_mode,
237242
)
238243

239244
record = {
@@ -320,7 +325,14 @@ def _aggregate_leg_events(
320325
# Attributionsfönster [lo, hi) för denna leg.
321326
lo = t if non_overlapping else swing.end.index
322327
hi = legs[i + 1][0] if (non_overlapping and i + 1 < len(legs)) else n
323-
streams = detect_level_events(df, swing, level_cfg, ratios, settings.pivots.atr_period)
328+
streams = detect_level_events(
329+
df,
330+
swing,
331+
level_cfg,
332+
ratios,
333+
settings.pivots.atr_period,
334+
scale_mode=settings.fib.scale_mode,
335+
)
324336
leg_events = 0
325337
for stream in streams:
326338
kept = [e for e in stream.events if lo <= e.bar_index < hi]

tests/research/test_human_review_level_events.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,36 @@ def test_collect_candidates_single_mode_uses_selected_swing(monkeypatch):
186186
assert all(r["event_bar"] > 40 for r in rows)
187187

188188

189+
def test_collect_candidates_honors_configured_log_scale(monkeypatch):
190+
"""Regression (PR #33 P2): collect_candidates must thread settings.fib.scale_mode
191+
into the detector and the level ladder, not silently fall back to linear."""
192+
from fibengine.core.fib import fib_levels
193+
from fibengine.research.human_review_rows import decode_levels
194+
195+
df = _trend_df()
196+
settings = _settings()
197+
assert settings.fib.scale_mode == "log" # repo default
198+
swing = _up_swing(df)
199+
monkeypatch.setattr(hr, "select_swing", lambda _df, _p, _s: swing)
200+
rows = collect_candidates(df, settings, mode="single")
201+
assert rows
202+
203+
ratios = settings.fib.levels
204+
log_ladder = {
205+
f"{r:g}": round(float(p), 6) for r, p in fib_levels(swing, ratios, scale_mode="log").items()
206+
}
207+
lin_ladder = {
208+
f"{r:g}": round(float(p), 6)
209+
for r, p in fib_levels(swing, ratios, scale_mode="linear").items()
210+
}
211+
assert log_ladder != lin_ladder # scales must differ for this swing, else the test is moot
212+
213+
for r in rows:
214+
decoded = {d["ratio"]: d["price"] for d in decode_levels(r)}
215+
assert decoded == log_ladder # ladder honors log scale, not linear
216+
assert r["fib_price"] == log_ladder[r["fib_level"]]
217+
218+
189219
def test_end_to_end_package_files_created(monkeypatch, tmp_path):
190220
df = _trend_df()
191221
settings = _settings()

0 commit comments

Comments
 (0)