A `backtesting.backtesting.Backtest` that supports fractional share trading by simple composition. It applies roughly the transformation: data = (data * fractional_unit).assign(Volume=data.Volume / fractional_unit) as left unchallenged in [this FAQ entry on GitHub](https://git
| 507 | |
| 508 | |
| 509 | class FractionalBacktest(Backtest): |
| 510 | """ |
| 511 | A `backtesting.backtesting.Backtest` that supports fractional share trading |
| 512 | by simple composition. It applies roughly the transformation: |
| 513 | |
| 514 | data = (data * fractional_unit).assign(Volume=data.Volume / fractional_unit) |
| 515 | |
| 516 | as left unchallenged in [this FAQ entry on GitHub](https://github.com/kernc/backtesting.py/issues/134), |
| 517 | then passes `data`, `args*`, and `**kwargs` to its super. |
| 518 | |
| 519 | Parameter `fractional_unit` represents the smallest fraction of currency that can be traded |
| 520 | and defaults to one [satoshi]. For μBTC trading, pass `fractional_unit=1/1e6`. |
| 521 | Thus-transformed backtest does a whole-sized trading of `fractional_unit` units. |
| 522 | |
| 523 | [satoshi]: https://en.wikipedia.org/wiki/Bitcoin#Units_and_divisibility |
| 524 | """ |
| 525 | def __init__(self, |
| 526 | data, |
| 527 | *args, |
| 528 | fractional_unit=1 / 100e6, |
| 529 | **kwargs): |
| 530 | if 'satoshi' in kwargs: |
| 531 | warnings.warn( |
| 532 | 'Parameter `FractionalBacktest(..., satoshi=)` is deprecated. ' |
| 533 | 'Use `FractionalBacktest(..., fractional_unit=)`.', |
| 534 | category=DeprecationWarning, stacklevel=2) |
| 535 | fractional_unit = 1 / kwargs.pop('satoshi') |
| 536 | self._fractional_unit = fractional_unit |
| 537 | self.__data: pd.DataFrame = data.copy(deep=False) # Shallow copy |
| 538 | for col in ('Open', 'High', 'Low', 'Close',): |
| 539 | self.__data[col] = self.__data[col] * self._fractional_unit |
| 540 | for col in ('Volume',): |
| 541 | self.__data[col] = self.__data[col] / self._fractional_unit |
| 542 | with warnings.catch_warnings(record=True): |
| 543 | warnings.filterwarnings(action='ignore', message='frac') |
| 544 | super().__init__(data, *args, **kwargs) |
| 545 | |
| 546 | def run(self, **kwargs) -> pd.Series: |
| 547 | with patch(self, '_data', self.__data): |
| 548 | result = super().run(**kwargs) |
| 549 | |
| 550 | trades: pd.DataFrame = result['_trades'] |
| 551 | trades['Size'] *= self._fractional_unit |
| 552 | trades[['EntryPrice', 'ExitPrice', 'TP', 'SL']] /= self._fractional_unit |
| 553 | |
| 554 | indicators = result['_strategy']._indicators |
| 555 | for indicator in indicators: |
| 556 | if indicator._opts['overlay']: |
| 557 | indicator /= self._fractional_unit |
| 558 | |
| 559 | return result |
| 560 | |
| 561 | |
| 562 | # Prevent pdoc3 documenting __init__ signature of Strategy subclasses |
no outgoing calls