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
79 changes: 77 additions & 2 deletions application/execution_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,61 @@ def _should_bootstrap_whole_share_buy(symbol, *, target_value, limit_price) -> b
return max(0.0, float(target_value or 0.0)) >= effective_limit_price * float(min_target_share_ratio)


def _should_top_up_existing_whole_share_buy(
symbol,
*,
target_value,
current_value,
quantity=0.0,
limit_price,
) -> bool:
del symbol
held_quantity = max(0.0, float(quantity or 0.0))
effective_limit_price = max(0.0, float(limit_price or 0.0))
if held_quantity < 1.0 or effective_limit_price <= 0.0:
return False
remaining_value = max(0.0, float(target_value or 0.0) - float(current_value or 0.0))
if remaining_value <= 0.0 or remaining_value >= effective_limit_price:
return False
held_whole_shares = int(held_quantity)
if held_whole_shares <= 0:
return False
target_quantity = max(0.0, float(target_value or 0.0)) / effective_limit_price
return target_quantity >= held_whole_shares + 0.5
Comment on lines +341 to +345

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 Use actual held shares when deciding top-ups

When the account already has fractional shares, flooring quantity to held_whole_shares makes the round-up threshold too low. For example, after any submitted sell, holding 2.4 shares at a $100 limit with a $250 target has only a 0.1-share shortfall, but this check treats it as 2 + 0.5 and submits another whole-share buy, leaving 3.4 shares versus a 2.5-share target. Since portfolio quantities are floats and notional/fractional holdings can exist, the top-up guard should either require an integral current quantity or compare the target against the actual held quantity.

Useful? React with 👍 / 👎.



def _planned_buy_order_quantity(
symbol,
*,
target_value,
current_value,
quantity=0.0,
buy_budget,
available_buying_power,
limit_price,
allow_top_up=False,
) -> int:
effective_limit_price = max(0.0, float(limit_price or 0.0))
if effective_limit_price <= 0.0:
return 0
planned_quantity = _floor_quantity(max(0.0, float(buy_budget or 0.0)) / effective_limit_price)
if planned_quantity > 0:
return planned_quantity
if not allow_top_up:
return 0
if max(0.0, float(available_buying_power or 0.0)) < effective_limit_price:
return 0
if _should_top_up_existing_whole_share_buy(
symbol,
target_value=target_value,
current_value=current_value,
quantity=quantity,
limit_price=effective_limit_price,
):
return 1
return 0


def _quote_price(market_data_port: MarketDataPort, symbol: str) -> float | None:
try:
price = float(market_data_port.get_quote(symbol).last_price)
Expand Down Expand Up @@ -573,6 +628,7 @@ def execute_value_target_plan(
skipped: list[dict[str, Any]] = []
reference_prices: dict[str, float] = {}
pending_sell_release_symbols: list[str] = []
sell_submitted = False

tradable_deltas: list[tuple[str, float, float]] = []
for symbol in sorted(set(targets) | set(market_values)):
Expand Down Expand Up @@ -631,6 +687,7 @@ def execute_value_target_plan(
max_notional_usd=max_order_notional_usd,
)
)
sell_submitted = True
investable_cash += quantity * sell_limit_price
continue

Expand All @@ -649,7 +706,16 @@ def execute_value_target_plan(
limit_price = _limit_buy_price(
symbol, price, limit_buy_premium, limit_buy_premium_by_symbol
)
quantity = _floor_quantity(buy_budget / limit_price) if limit_price > 0 else 0
quantity = _planned_buy_order_quantity(
symbol,
target_value=delta_value + market_values.get(symbol, 0.0),
current_value=market_values.get(symbol, 0.0),
quantity=current_quantities.get(symbol, 0.0),
buy_budget=buy_budget,
available_buying_power=investable_cash if order_notional_cap is None else min(investable_cash, order_notional_cap),
limit_price=limit_price,
allow_top_up=sell_submitted,
)
if quantity > 0:
estimated_buy_cost += quantity * limit_price
if estimated_buy_cost > investable_cash:
Expand Down Expand Up @@ -701,7 +767,16 @@ def execute_value_target_plan(
investable_cash = max(0.0, investable_cash - buy_budget)
continue
limit_price = _limit_buy_price(symbol, price, limit_buy_premium, limit_buy_premium_by_symbol)
quantity = _floor_quantity(buy_budget / limit_price) if limit_price > 0 else 0
quantity = _planned_buy_order_quantity(
symbol,
target_value=targets.get(symbol, 0.0),
current_value=market_values.get(symbol, 0.0),
quantity=current_quantities.get(symbol, 0.0),
buy_budget=buy_budget,
available_buying_power=investable_cash if order_notional_cap is None else min(investable_cash, order_notional_cap),
limit_price=limit_price,
allow_top_up=sell_submitted,
)
if quantity <= 0:
if order_notional_cap is None and investable_cash < limit_price:
skipped.append(
Expand Down
29 changes: 29 additions & 0 deletions tests/test_execution_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,35 @@ def test_execute_value_target_plan_keeps_existing_whole_share_when_positive_targ
]


def test_execute_value_target_plan_tops_up_existing_whole_share_when_target_rounds_up():
execution_port = FakeExecutionPort()
result = execute_value_target_plan(
plan={
"allocation": {
"targets": {"SOXL": 0.0, "SOXX": 260.0},
"risk_symbols": ("SOXL", "SOXX"),
},
"portfolio": {
"market_values": {"SOXL": 120.0, "SOXX": 200.0},
"quantities": {"SOXL": 3.0, "SOXX": 2.0},
"sellable_quantities": {"SOXL": 3.0, "SOXX": 2.0},
"liquid_cash": 10.0,
},
"execution": {"current_min_trade": 10.0, "investable_cash": 10.0},
},
market_data_port=FakeMarketDataPort({"SOXL": 40.0, "SOXX": 100.0}),
execution_port=execution_port,
dry_run_only=True,
limit_buy_premium=1.0,
)

assert result.action_done is True
assert [(order.side, order.symbol, order.quantity) for order in execution_port.orders] == [
("sell", "SOXL", 3.0),
("buy", "SOXX", 1.0),
]


def test_execute_value_target_plan_skips_when_cap_cannot_buy_one_share():
execution_port = FakeExecutionPort()
result = execute_value_target_plan(
Expand Down
Loading