Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions application/execution_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ class ExecutionCycleResult:

DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD = 2000.0
SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS = frozenset({"TQQQ", "SOXL"})


def _noop_sleep(_seconds):
Expand Down Expand Up @@ -520,12 +521,30 @@ def _apply_small_account_whole_share_compatibility(
continue
if price > 0.0:
quote_prices[symbol] = price
retained_symbols = []
portfolio = dict((plan or {}).get("portfolio") or {})
quantities = {
str(symbol or "").strip().upper(): float(quantity or 0.0)
for symbol, quantity in dict(portfolio.get("quantities") or {}).items()
}
compatibility_targets = {
str(symbol or "").strip().upper(): float(value or 0.0)
for symbol, value in target_values.items()
}
for symbol in candidate_symbols:
if symbol not in SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS:
continue
target_value = max(0.0, float(compatibility_targets.get(symbol, 0.0) or 0.0))
price = max(0.0, float(quote_prices.get(symbol, 0.0) or 0.0))
if price > 0.0 and 0.0 < target_value < price and quantities.get(symbol, 0.0) >= 1.0:
compatibility_targets[symbol] = price

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid retaining an extra share when quotes tick up

When the order quote is even slightly above the portfolio snapshot's per-share valuation, setting the retained target to exactly price makes _sell_order_quantity compute (current_value - price) / price and then floor it below held - 1; for example, 7 TQQQ shares valued at $541.31 with a fresh quote of $77.34 sells only 5 shares, leaving 2 instead of the intended minimum whole share. Derive the retained target/order quantity from the held quantity, or otherwise account for quote/snapshot drift before flooring.

Useful? React with 👍 / 👎.

retained_symbols.append(symbol)
safe_haven_symbols = _safe_haven_cash_symbols(
portfolio=dict((plan or {}).get("portfolio") or {}),
portfolio=portfolio,
allocation=allocation,
)
compatibility = apply_small_account_cash_compatibility(
target_values,
compatibility_targets,
quote_prices,
candidate_symbols=candidate_symbols,
safe_haven_cash_symbols=safe_haven_symbols,
Expand Down Expand Up @@ -554,10 +573,14 @@ def _apply_small_account_whole_share_compatibility(
adjusted_allocation["small_account_whole_share_substituted_symbols"] = substituted
if safe_haven_substituted:
adjusted_allocation["small_account_safe_haven_cash_substituted_symbols"] = tuple(safe_haven_substituted)
if retained_symbols:
adjusted_allocation["small_account_existing_whole_share_retained_symbols"] = tuple(
dict.fromkeys(retained_symbols)
)
if cash_substitution_notes:
adjusted_allocation["small_account_whole_share_cash_notes"] = cash_substitution_notes
adjusted_plan = dict(plan or {})
if substituted or safe_haven_substituted:
if substituted or safe_haven_substituted or retained_symbols:
adjusted_plan["allocation"] = adjusted_allocation
return adjusted_plan, adjusted_allocation

Expand Down
69 changes: 69 additions & 0 deletions tests/test_rebalance_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,75 @@ def test_tqqq_delevered_qqqm_target_is_executable_for_small_account(self):
self.assertIn("限价买入] QQQM: 1股", sent_messages[0])
self.assertNotIn("QQQM.US 目标金额 $507.87 低于 1 股价格", sent_messages[0])

def test_existing_tqqq_retention_below_one_share_keeps_min_whole_share(self):
plan = _build_plan(
strategy_profile="tqqq_growth_income",
strategy_symbols=("TQQQ", "QQQM", "BOXX", "SCHD", "DGRO", "SGOV", "SPYI", "QQQI"),
risk_symbols=("TQQQ", "QQQM"),
income_symbols=("SCHD", "DGRO", "SGOV", "SPYI", "QQQI"),
safe_haven_symbols=("BOXX",),
targets={
"TQQQ": 60.94,
"QQQM": 320.00,
"BOXX": 0.0,
"SCHD": 0.0,
"DGRO": 0.0,
"SGOV": 0.0,
"SPYI": 0.0,
"QQQI": 0.0,
},
market_values={
"TQQQ": 541.31,
"QQQM": 0.0,
"BOXX": 0.0,
"SCHD": 0.0,
"DGRO": 0.0,
"SGOV": 0.0,
"SPYI": 0.0,
"QQQI": 0.0,
},
sellable_quantities={"TQQQ": 7, "QQQM": 0, "BOXX": 0, "SCHD": 0, "DGRO": 0, "SGOV": 0, "SPYI": 0, "QQQI": 0},
quantities={"TQQQ": 7, "QQQM": 0, "BOXX": 0, "SCHD": 0, "DGRO": 0, "SGOV": 0, "SPYI": 0, "QQQI": 0},
current_min_trade=100.0,
trade_threshold_value=100.0,
investable_cash=528.91,
market_status="",
deploy_ratio_text="",
income_ratio_text="",
income_locked_ratio_text="",
signal_message="🚀 入场信号",
available_cash=539.70,
total_strategy_equity=539.70,
portfolio_rows=(("TQQQ", "QQQM", "BOXX"), ("SCHD", "DGRO", "SGOV", "SPYI", "QQQI")),
benchmark_symbol="QQQ",
benchmark_price=722.05,
long_trend_value=626.87,
exit_line=626.87,
)

sent_messages, _, _ = self._run_strategy(
plan,
prices={
"TQQQ.US": 77.33,
"QQQM.US": 297.19,
"BOXX.US": 116.95,
"SCHD.US": 80.0,
"DGRO.US": 65.0,
"SGOV.US": 100.0,
"SPYI.US": 52.0,
"QQQI.US": 52.0,
},
estimate_max_purchase_quantity_value=10,
strategy_display_name="TQQQ 增长收益",
)

self.assertEqual(len(sent_messages), 1)
self.assertIn("🔔 【调仓指令】", sent_messages[0])
self.assertIn("限价卖出] TQQQ: 6股 @ $76.94", sent_messages[0])
self.assertNotIn("限价卖出] TQQQ: 7股", sent_messages[0])
self.assertIn("限价买入] QQQM: 1股 @ $298.68", sent_messages[0])
self.assertNotIn("TQQQ.US 目标金额 $60.94 低于 1 股价格", sent_messages[0])

def test_target_gap_below_one_share_does_not_report_cash_shortage(self):
plan = _build_plan(
strategy_symbols=("SOXL", "SOXX", "BOXX", "QQQI", "SPYI"),
Expand Down