Skip to content

ENH: Add fixed commission option to _Broker (see #623) #662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
48 changes: 31 additions & 17 deletions backtesting/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,12 +664,15 @@ def __set_contingent(self, type, price):


class _Broker:
def __init__(self, *, data, cash, commission, margin,
trade_on_close, hedging, exclusive_orders, index):
def __init__(self, *, data, cash, commission, margin, trade_on_close,
hedging, exclusive_orders, use_fixed_commission, index):
assert 0 < cash, f"cash should be >0, is {cash}"
assert -.1 <= commission < .1, \
("commission should be between -10% "
f"(e.g. market-maker's rebates) and 10% (fees), is {commission}")
if use_fixed_commission:
assert commission >= 0, f"fixed commission should be be >0, is {commission}"
else:
assert -.1 <= commission < .1, \
("commission should be between -10% "
f"(e.g. market-maker's rebates) and 10% (fees), is {commission}")
assert 0 < margin <= 1, f"margin should be between 0 and 1, is {margin}"
self._data: _Data = data
self._cash = cash
Expand All @@ -678,6 +681,7 @@ def __init__(self, *, data, cash, commission, margin,
self._trade_on_close = trade_on_close
self._hedging = hedging
self._exclusive_orders = exclusive_orders
self._use_fixed_commission = use_fixed_commission

self._equity = np.tile(np.nan, len(index))
self.orders: List[Order] = []
Expand Down Expand Up @@ -745,10 +749,13 @@ def last_price(self) -> float:

def _adjusted_price(self, size=None, price=None) -> float:
"""
Long/short `price`, adjusted for commisions.
Long/short `price`, adjusted for commissions (fixed or percentage).
In long positions, the adjusted price is a fraction higher, and vice versa.
"""
return (price or self.last_price) * (1 + copysign(self._commission, size))
if self._use_fixed_commission:
return ((price or self.last_price) + copysign(self._commission, size))
else:
return ((price or self.last_price) * (1 + copysign(self._commission, size)))

@property
def equity(self) -> float:
Expand Down Expand Up @@ -989,7 +996,8 @@ def __init__(self,
margin: float = 1.,
trade_on_close=False,
hedging=False,
exclusive_orders=False
exclusive_orders=False,
use_fixed_commission=False
):
"""
Initialize a backtest. Requires data and a strategy to test.
Expand All @@ -1011,11 +1019,13 @@ def __init__(self,

`cash` is the initial cash to start with.

`commission` is the commission ratio. E.g. if your broker's commission
is 1% of trade value, set commission to `0.01`. Note, if you wish to
account for bid-ask spread, you can approximate doing so by increasing
the commission, e.g. set it to `0.0002` for commission-less forex
trading where the average spread is roughly 0.2‰ of asking price.
`commission` is the commission ratio, a percentage by default.E.g.
if your broker's commission is 1% of trade value, set commission to `0.01`.
Note, if you wish to account for bid-ask spread, you can approximate
doing so by increasing the commission, e.g. set it to `0.0002` for
commission-less forex trading where the average spread is roughly
0.2‰ of asking price. If you want to use a fixed commission in dollar
value, set the `use_fixed_commission` parameter as `True`.

`margin` is the required margin (ratio) of a leveraged account.
No difference is made between initial and maintenance margins.
Expand All @@ -1034,6 +1044,9 @@ def __init__(self,
trade/position, making at most a single trade (long or short) in effect
at each time.

If `use_fixed_commission` is `True`, the commission won't be considered
as a percentage, but as a flat fee on every order.

[FIFO]: https://www.investopedia.com/terms/n/nfa-compliance-rule-2-43b.asp
"""

Expand All @@ -1042,8 +1055,9 @@ def __init__(self,
if not isinstance(data, pd.DataFrame):
raise TypeError("`data` must be a pandas.DataFrame with columns")
if not isinstance(commission, Number):
raise TypeError('`commission` must be a float value, percent of '
'entry order price')
raise TypeError('`commission` must be a number, percent of '
'entry order price. Set `use_fixed_commission` to `True` '
'if you want to use a flat fee instead')

data = data.copy(deep=False)

Expand Down Expand Up @@ -1087,8 +1101,8 @@ def __init__(self,
self._data: pd.DataFrame = data
self._broker = partial(
_Broker, cash=cash, commission=commission, margin=margin,
trade_on_close=trade_on_close, hedging=hedging,
exclusive_orders=exclusive_orders, index=data.index,
trade_on_close=trade_on_close, hedging=hedging, exclusive_orders=exclusive_orders,
use_fixed_commission=use_fixed_commission, index=data.index
)
self._strategy = strategy
self._results: Optional[pd.Series] = None
Expand Down