Skip to content

Commit f0ea5ff

Browse files
authored
Merge pull request #65 from QuantStrategyLab/codex/cash-sweep-helper-ibkr
Use shared cash sweep whole-share helper
2 parents 2bde8fa + 95da3cb commit f0ea5ff

2 files changed

Lines changed: 52 additions & 13 deletions

File tree

application/execution_service.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import math
65
import hashlib
76
import json
87
import tempfile
@@ -12,6 +11,35 @@
1211
from typing import Any
1312

1413
import pandas as pd
14+
try:
15+
from quant_platform_kit.common.cash_sweep import should_sell_cash_sweep_to_fund_whole_share_buy
16+
except ImportError: # pragma: no cover - compatibility with older pinned shared wheels
17+
def should_sell_cash_sweep_to_fund_whole_share_buy(
18+
max_quantity,
19+
cash_sweep_price,
20+
base_buying_power,
21+
funding_needs,
22+
):
23+
if max_quantity <= 0:
24+
return False
25+
sweep_price = float(cash_sweep_price or 0.0)
26+
if sweep_price <= 0.0:
27+
return False
28+
current_buying_power = max(0.0, float(base_buying_power or 0.0))
29+
sweep_capacity = float(max_quantity) * sweep_price
30+
if sweep_capacity <= 0.0:
31+
return False
32+
33+
for underweight_value, ask_price in funding_needs:
34+
_ = underweight_value
35+
quote_price = float(ask_price or 0.0)
36+
if quote_price <= 0.0:
37+
continue
38+
if current_buying_power >= quote_price:
39+
return False
40+
if current_buying_power + sweep_capacity >= quote_price:
41+
return True
42+
return False
1543
from quant_platform_kit.common.quantity import (
1644
floor_to_quantity_step,
1745
format_quantity,
@@ -663,23 +691,27 @@ def cash_sweep_sale_quantity_to_fund_buy(max_quantity: int, candidate_symbols: t
663691
if max_quantity <= 0 or not safe_haven_symbol or cash_sweep_price <= 0.0:
664692
return 0
665693
base_buying_power = max(0.0, float(anticipated_buying_power))
694+
funding_needs = []
666695
for symbol in candidate_symbols:
667696
underweight_value = target_mv[symbol] - current_mv.get(symbol, 0.0)
668697
if underweight_value <= threshold:
669698
continue
670699
ask = prices.get(symbol)
671700
if not ask or ask <= 0.0:
672701
continue
673-
max_buy_quantity = int(underweight_value // ask)
674-
if max_buy_quantity <= 0:
675-
continue
676-
required_buying_power = max_buy_quantity * ask * 1.0
677-
if base_buying_power >= required_buying_power:
678-
return 0
679-
return min(
680-
max_quantity,
681-
max(1, math.ceil((required_buying_power - base_buying_power) / cash_sweep_price)),
702+
funding_needs.append(
703+
(
704+
underweight_value,
705+
round(ask * limit_buy_premium, 2),
706+
)
682707
)
708+
if should_sell_cash_sweep_to_fund_whole_share_buy(
709+
float(max_quantity),
710+
cash_sweep_price,
711+
base_buying_power,
712+
funding_needs,
713+
):
714+
return int(max_quantity)
683715
return 0
684716

685717
has_sell_plan = False

tests/test_execution_service.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,15 @@ def fills(self):
482482

483483
def accountValues(self):
484484
self._account_values_calls += 1
485-
buying_power = "124" if self._account_values_calls == 1 else "324"
486-
return [SimpleNamespace(tag="AvailableFunds", currency="USD", value=buying_power)]
485+
buying_power = "24" if self._account_values_calls == 1 else "224"
486+
return [
487+
SimpleNamespace(
488+
account="DU123",
489+
tag="AvailableFunds",
490+
currency="USD",
491+
value=buying_power,
492+
)
493+
]
487494

488495
submitted = []
489496

@@ -497,7 +504,7 @@ def fake_submit_order_intent(_ib, intent):
497504
FakeIB(),
498505
{"VOO": 0.8, "BOXX": 0.2},
499506
{"VOO": {"quantity": 0}, "BOXX": {"quantity": 2}},
500-
{"equity": 1000.0, "buying_power": 124.0},
507+
{"equity": 1000.0, "buying_power": 24.0},
501508
fetch_quote_snapshots=lambda *_args, **_kwargs: {
502509
"VOO": SimpleNamespace(last_price=100.0),
503510
"BOXX": SimpleNamespace(last_price=100.0),

0 commit comments

Comments
 (0)