Skip to content

Commit 733d282

Browse files
committed
Add subtract_fees config option.
When enabled, fees are subtracted from cash balance after an order is filled.
1 parent 17d45d4 commit 733d282

File tree

4 files changed

+45
-0
lines changed

4 files changed

+45
-0
lines changed

src/pybroker/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class StrategyConfig:
2929
:class:`pybroker.common.FeeInfo`.
3030
- ``None``: Fees are disabled (default).
3131
fee_amount: Brokerage fee amount.
32+
subtract_fees: Whether to subtract fees from the cash balance after an
33+
order is filled. Defaults to ``False``.
3234
enable_fractional_shares: Whether to enable trading fractional shares.
3335
Set to ``True`` for crypto trading. Defaults to ``False``.
3436
round_fill_price: Whether to round fill prices to the nearest cent.
@@ -74,6 +76,7 @@ class StrategyConfig:
7476
default=None
7577
)
7678
fee_amount: float = field(default=0)
79+
subtract_fees: bool = field(default=False)
7780
enable_fractional_shares: bool = field(default=False)
7881
round_fill_price: bool = field(default=True)
7982
max_long_positions: Optional[int] = field(default=None)

src/pybroker/portfolio.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ class Portfolio:
293293
the amount of equity held in cash and long positions added together
294294
with the unrealized PnL of all open short positions.
295295
fees: Current brokerage fees.
296+
fee_amount: Brokerage fee amount.
297+
subtract_fees: Whether to subtract fees from the cash balance.
296298
enable_fractional_shares: Whether to enable trading fractional shares.
297299
orders: ``deque`` of all filled orders, sorted in ascending
298300
chronological order.
@@ -318,6 +320,7 @@ def __init__(
318320
Union[FeeMode, Callable[[FeeInfo], Decimal], None]
319321
] = None,
320322
fee_amount: Optional[float] = None,
323+
subtract_fees: bool = False,
321324
enable_fractional_shares: bool = False,
322325
max_long_positions: Optional[int] = None,
323326
max_short_positions: Optional[int] = None,
@@ -328,6 +331,7 @@ def __init__(
328331
self._fee_amount: Optional[Decimal] = (
329332
None if fee_amount is None else to_decimal(fee_amount)
330333
)
334+
self._subtract_fees = subtract_fees
331335
self._enable_fractional_shares = enable_fractional_shares
332336
self.equity: Decimal = self.cash
333337
self.market_value: Decimal = self.cash
@@ -440,6 +444,8 @@ def _add_order(
440444
)
441445
self.orders.append(order)
442446
self.fees += fees
447+
if self._subtract_fees:
448+
self.cash -= fees
443449
return order
444450

445451
def _add_trade(
@@ -519,6 +525,8 @@ def _remove_stop_data(self, entry: Entry):
519525
del self._stop_data[stop.id]
520526

521527
def _clamp_shares(self, fill_price: Decimal, shares: Decimal) -> Decimal:
528+
if self.cash < 0:
529+
return Decimal()
522530
max_shares = (
523531
Decimal(self.cash / fill_price)
524532
if self._enable_fractional_shares

src/pybroker/strategy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,7 @@ def walkforward(
12301230
self._config.initial_cash,
12311231
self._config.fee_mode,
12321232
self._config.fee_amount,
1233+
self._config.subtract_fees,
12331234
self._fractional_shares_enabled(),
12341235
self._config.max_long_positions,
12351236
self._config.max_short_positions,

tests/test_portfolio.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,39 @@ def test_buy_and_sell_when_fees(
793793
assert portfolio.fees == expected_buy_fees + expected_sell_fees
794794

795795

796+
def test_subtract_fees():
797+
portfolio = Portfolio(
798+
3, FeeMode.PER_ORDER, fee_amount=1, subtract_fees=True
799+
)
800+
order = portfolio.buy(DATE_1, SYMBOL_1, shares=1, fill_price=1)
801+
assert_order(
802+
order=order,
803+
date=DATE_1,
804+
symbol=SYMBOL_1,
805+
type="buy",
806+
limit_price=None,
807+
fill_price=1,
808+
shares=1,
809+
fees=1,
810+
)
811+
assert portfolio.cash == 1
812+
order = portfolio.buy(DATE_2, SYMBOL_1, shares=1, fill_price=1)
813+
assert_order(
814+
order=order,
815+
date=DATE_2,
816+
symbol=SYMBOL_1,
817+
type="buy",
818+
limit_price=None,
819+
fill_price=1,
820+
shares=1,
821+
fees=1,
822+
)
823+
assert portfolio.cash == -1
824+
order = portfolio.buy(DATE_2, SYMBOL_1, shares=1, fill_price=1)
825+
assert order is None
826+
assert portfolio.cash == -1
827+
828+
796829
def test_sell_when_partial_shares():
797830
portfolio = Portfolio(CASH)
798831
buy_order = portfolio.buy(

0 commit comments

Comments
 (0)