feat: Add unrealized loss based early taker close#122
Conversation
When a position's unrealized loss exceeds a configurable threshold (in bps), PositionCloser triggers an immediate taker close instead of waiting for the age-based force-close. This caps large adverse moves that currently account for 55% of total losses. Default: 0 (disabled, backward compatible). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
keitaj
left a comment
There was a problem hiding this comment.
Review: Unrealized loss early taker close
Clean, well-scoped feature with solid test coverage. The implementation follows existing patterns in PositionCloser correctly, backward compatibility is preserved via default=0, and the bps calculation is correct for both long and short positions.
1. Missing _consecutive_close_failures cleanup (nit)
strategies/mm_position_closer.py L253-256:
When the unrealized loss path closes a position, it pops _open_positions but does not clean up _consecutive_close_failures — unlike the force-close path (L342, L372) and cleanup_closed() (L161) which both do.
self._record_close(coin, CLOSE_REASON_UNREALIZED_LOSS, age, current_tier)
close_position_fn(coin)
self._open_positions.pop(coin, None)
returnThe stale entry gets cleaned up on the next successful close placement for that coin, so this is not a functional bug — just an inconsistency with the other close paths.
Suggestion:
self._record_close(coin, CLOSE_REASON_UNREALIZED_LOSS, age, current_tier)
close_position_fn(coin)
self._open_positions.pop(coin, None)
self._consecutive_close_failures.pop(coin, None)
returnVerdict: LGTM
Single nit (non-blocking). Logic is correct, risk guards untouched, tests are thorough (9 cases covering all edge cases), and CLI/config wiring is complete. Ready to merge after CI passes.
Summary
--unrealized-loss-close-bps) to PositionCloser: when a position's unrealized loss exceeds the configured bps, it is immediately closed via taker order regardless of position ageChanges
strategies/mm_position_closer.pyCLOSE_REASON_UNREALIZED_LOSSconstant,unrealized_loss_close_bpsparameter, and unrealized loss check inmanage()before age-based force closestrategies/market_making_strategy.pyunrealized_loss_close_bpsfrom config to PositionCloserbot.py--unrealized-loss-close-bpsCLI argument and wire it into strategy paramsREADME.mdtests/test_unrealized_loss_close.pyTest plan
🤖 Generated with Claude Code