Skip to content

fix(regime): round partial hard-band steps up so zero-target exits finish - #719

Merged
brndnmtthws merged 1 commit into
mainfrom
fix/hard-band-ceil
Oct 16, 2026
Merged

brndnmtthws merged 1 commit into
mainfrom
fix/hard-band-ceil

Conversation

@brndnmtthws

Copy link
Copy Markdown
Owner

Defect

With hard_band_rebalance_fraction < 1 (e.g. 0.5), a hard-band step sizes each order as round(gap * fraction). Python rounds half to even, so a one-share gap gives round(-0.5) == 0. A position whose target weight is zero therefore keeps its last share forever. Because a zero target with a live position counts as full drift (relative_drift = 1.0), that share sets off a hard rebalance on every run, which keeps re-sizing (and churning) the rest of the basket.

Live evidence

Seen in a live regime-rebalance account during a long zero-target (risk-off) spell for one symbol: the exit halved each run (… → 123 → 61 → 30 → 15 → 7 → 3 → 1) and then stopped at one share. After that, every run was a hard rebalance, about 5.6 orders per session on average, until the symbol's target turned positive again. A replay of 2018–2026 showed no measurable CAGR/drawdown effect, so this fixes order hygiene and a stuck state. It is not a returns change.

Fix

Round the step magnitude up, with a 1e-9 tolerance so float products such as 10 * 0.3 don't round up a whole share:

desired = int(math.copysign(math.ceil(abs(desired) * rebalance_fraction - 1e-9), desired))

Every hard step now moves at least one share toward the target and never past it (ceil(|gap| * f) <= |gap| for f <= 1). Soft-band, flow and deficit paths are unchanged, a full step (fraction == 1.0) is unchanged, and exits stay staged (492 → 246 → 123 → 61 → 30 → 15 → 7 → 3 → 1 → 0).

Behavioral impact: odd partial gaps now trade one share more than before (e.g. 3 of a 5-share gap instead of 2). Risk controls are unaffected.

Tests

New tests in tests/test_regime_rebalance.py:

  • zero target with one share sells it on the hard band
  • 5-share gaps at fraction 0.5 trade 3 and never overshoot
  • a 492-share zero-target exit finishes at 0 instead of stranding one share

All three fail on main (077e457) and pass with the fix. Existing partial-rebalance tests use even gaps and are unchanged.

Validation run locally:

  • uv run pytest: 821 passed
  • uv run ruff check ., uv run ruff format --check ., uv run ty check: clean

Merge caution

Deployments that track the main image pick this up on their next scheduled run. It changes live order sizing, so merge deliberately.

…nish

Half-to-even rounding of a one-share gap at hard_band_rebalance_fraction 0.5
gives round(-0.5) == 0, so a zero-target position stranded its last share.
That share's full drift re-triggered a hard rebalance on every run. Round the
step magnitude up (1e-9 tolerance) so each hard step moves at least one share
toward the target and never past it. Soft, flow and deficit paths unchanged.
@brndnmtthws
brndnmtthws marked this pull request as ready for review October 16, 2026 20:43
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T20:44:46.802970Z f1e3d86 Draft marked ready
🔒 Security Review ✅ Completed 2026-10-16T20:45:26.265099Z f1e3d86 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@brndnmtthws
brndnmtthws merged commit 35e66b5 into main Oct 16, 2026
11 checks passed
@brndnmtthws
brndnmtthws deleted the fix/hard-band-ceil branch October 16, 2026 20:49
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.

1 participant