backtesting.py icon indicating copy to clipboard operation
backtesting.py copied to clipboard

Crypto issue with fractional size

Open apollace opened this issue 1 month ago • 2 comments

Enhancement description

Due to the high value of single crypto coins, e.g. BTC, it is impossible to do a backtest with absolute quantities instead of fractional quantities. As soon as the size is below 0 it is not an absolute values anymore.

Code sample

# This means 10% of my portfolio, what if I want to buy 0.1 BTC? 
self.buy(size=0.1, limit=buy_price, sl=buy_price * (1 - self.sl))

Additional info, images

No response

apollace avatar Nov 04 '25 11:11 apollace

Have you seen backtesting.lib.FractionalBacktest?

kernc avatar Nov 04 '25 14:11 kernc

@kernc I believe that FractionalBacktest is not implemented correctly.

If we execute buy on signal 1 and sell on signal -1

Given this data:

Image

With this code:

from backtesting.test import BTCUSD
import numpy as np
from backtesting import Strategy
from backtesting.lib import FractionalBacktest

data = BTCUSD.tail(10).copy()
signals = np.zeros(len(data), dtype=int)
signals[1] = 1
signals[8] = -1
data["Signal"] = signals

class FirstStrategy(Strategy):

    def init(self):
        pass

    def next(self):
        signal = self.data.Signal[-1]
        price = self.data.Close[-1]

        if signal == 1 and not self.position:
            print(f"BUYING  at {price}")
            self.trade = self.buy(size=1)
        elif signal == -1:
            if self.position:
                print(f"SELLING {self.position.size} BTC at {price}")
                self.position.close()

bt = FractionalBacktest(data, FirstStrategy, cash=100_000, commission=0.007)
stats = bt.run()
bt.plot()
print(stats)

This is the output:

BUYING  at 0.0005915
SELLING 1 BTC at 0.00072346

Start                     2024-03-31 00:00:00
End                       2024-12-31 00:00:00
Duration                    275 days 00:00:00
Exposure Time [%]                        80.0
Equity Final [$]                 100000.00037
Equity Peak [$]                  100000.00038
Commissions [$]                           0.0
Return [%]                                0.0
Buy & Hold Return [%]                31.45958
Return (Ann.) [%]                         0.0
Volatility (Ann.) [%]                     0.0
CAGR [%]                                  0.0
Sharpe Ratio                              NaN
Sortino Ratio                         4.50958
Calmar Ratio                          5.12325
Alpha [%]                                 0.0
Beta                                      0.0
Max. Drawdown [%]                        -0.0
Avg. Drawdown [%]                        -0.0
Max. Drawdown Duration      153 days 00:00:00
Avg. Drawdown Duration      153 days 00:00:00
# Trades                                    1
Win Rate [%]                            100.0
Best Trade [%]                       62.95813
Worst Trade [%]                      62.95813
Avg. Trade [%]                       62.95813
Max. Trade Duration         214 days 00:00:00
Avg. Trade Duration         214 days 00:00:00
Profit Factor                             NaN
Expectancy [%]                       62.95813
SQN                                       NaN
Kelly Criterion                           NaN
_strategy                       FirstStrategy
_equity_curve                             ...
_trades                             Size  ...
dtype: object

As you can see equity is not correct. I believe it's because of the how the size is implemented in the class , and that messes up everything.

this is the trade executed stats[_trades]:

Size EntryBar ExitBar EntryPrice ExitPrice SL TP PnL Commission ReturnPct EntryTime ExitTime Duration Tag
0 1.000000e-08 2 9 59160.0 96515.0 NaN NaN 0.000363 0.000011 0.613003 2024-05-31 2024-12-31 214 days None

ivandotv avatar Nov 17 '25 19:11 ivandotv