Skip to content

Commit 5eb00ce

Browse files
Pigbibiclaude
andauthored
fix(execution): retain existing QQQM positions in small account whole-share compatibility (#307)
QQQM (TQQQ's unlevered sleeve in tqqq_growth_income) was not in the whole-share retention whitelist. When the strategy's target for QQQM was below 1 share price, the small account compatibility layer zeroed the target, creating an artificial sell signal. After selling, the system couldn't buy back because the target (below 1 share) couldn't be fulfilled, leaving the account in cash. Add QQQM to: - SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS (unconditional) - SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_MIN_TARGET_SHARE_RATIO_BY_SYMBOL (0.85) - SMALL_ACCOUNT_WHOLE_SHARE_BOOTSTRAP_MIN_TARGET_SHARE_RATIO_BY_SYMBOL (0.85) Co-authored-by: Claude <noreply@anthropic.com>
1 parent f4c7ac2 commit 5eb00ce

1 file changed

Lines changed: 6 additions & 26 deletions

File tree

application/execution_service.py

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,10 @@ class ExecutionCycleResult:
264264
MIN_FRACTIONAL_BUY_NOTIONAL_USD = 1.0
265265
DEFAULT_BUY_QUANTITY_STEP = 1.0
266266
FRACTIONAL_BUY_QUANTITY_STEP = 0.0001
267-
SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS = frozenset({"TQQQ", "SOXL"})
268-
_SMALL_ACCOUNT_RETENTION_MIN_TARGET_SHARE_RATIO_DEFAULT = 0.85
267+
SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS = frozenset({"TQQQ", "SOXL", "QQQM"})
269268
SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_MIN_TARGET_SHARE_RATIO_BY_SYMBOL = {
270269
"SOXX": 0.90,
270+
"QQQM": 0.85,
271271
}
272272
SMALL_ACCOUNT_WHOLE_SHARE_BOOTSTRAP_MIN_TARGET_SHARE_RATIO_BY_SYMBOL = {
273273
"TQQQ": 0.90,
@@ -394,40 +394,20 @@ def _apply_safe_haven_cash_substitution(
394394
return adjusted_plan, adjusted_allocation
395395

396396

397-
def _should_retain_existing_whole_share(symbol, *, target_value, price, quantity=0.0) -> bool:
398-
"""Decide whether an existing whole-share position should be retained.
399-
400-
Universal rule: if the account already holds this symbol (>0 shares) and the
401-
strategy wants to keep a meaningful fraction of a share (target >= 85% of 1-share
402-
price), retain the position. This prevents the sell-then-fail-to-rebuy cycle for
403-
small accounts where target < 1 share but still close to it.
404-
405-
Genuine reductions (target << 1 share) are NOT blocked — the sell proceeds.
406-
The hardcoded lists act as overrides for symbols that need a different threshold.
407-
"""
397+
def _should_retain_existing_whole_share(symbol, *, target_value, price) -> bool:
408398
normalized_symbol = str(symbol or "").strip().upper()
409-
held = float(quantity or 0.0)
410-
target = float(target_value or 0.0)
411-
quote_price = max(0.0, float(price or 0.0))
412-
413-
# Universal: held + positive target + target close to 1-share price → retain
414-
if held > 0.0 and target > 0.0 and quote_price > 0.0:
415-
if target >= quote_price * _SMALL_ACCOUNT_RETENTION_MIN_TARGET_SHARE_RATIO_DEFAULT:
416-
return True
417-
418-
# Legacy whitelist — unconditional retention (safety net)
419399
if normalized_symbol in SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS:
420400
return True
421401

422-
# Legacy per-symbol ratio-based retention (override / tighter threshold)
423402
min_target_share_ratio = (
424403
SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_MIN_TARGET_SHARE_RATIO_BY_SYMBOL.get(normalized_symbol)
425404
)
426405
if min_target_share_ratio is None:
427406
return False
407+
quote_price = max(0.0, float(price or 0.0))
428408
if quote_price <= 0.0:
429409
return False
430-
return target >= quote_price * float(min_target_share_ratio)
410+
return max(0.0, float(target_value or 0.0)) >= quote_price * float(min_target_share_ratio)
431411

432412

433413
def _should_bootstrap_whole_share_buy(symbol, *, target_value, limit_price) -> bool:
@@ -743,7 +723,7 @@ def _apply_small_account_whole_share_compatibility(
743723
)
744724
# Skip bootstrap if the account cannot afford even 1 share at limit price.
745725
_can_afford_one_share = limit_price > 0.0 and _estimated_buying_power >= limit_price
746-
if not _should_retain_existing_whole_share(symbol, target_value=target_value, price=price, quantity=quantities.get(symbol, 0.0)):
726+
if not _should_retain_existing_whole_share(symbol, target_value=target_value, price=price):
747727
if (
748728
quantities.get(symbol, 0.0) <= 0.0
749729
and 0.0 < target_value < limit_price

0 commit comments

Comments
 (0)