:mag_right: :chart_with_upwards_trend: :snake: :moneybag: Backtest trading strategies in Python.

Overview

Backtesting.py

Build Status Code Coverage Backtesting on PyPI PyPI downloads GitHub Sponsors

Backtest trading strategies with Python.

Project website

Documentation

Star the project if you use it.

Installation

$ pip install backtesting

Usage

from backtesting import Backtest, Strategy
from backtesting.lib import crossover

from backtesting.test import SMA, GOOG


class SmaCross(Strategy):
    def init(self):
        price = self.data.Close
        self.ma1 = self.I(SMA, price, 10)
        self.ma2 = self.I(SMA, price, 20)

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.buy()
        elif crossover(self.ma2, self.ma1):
            self.sell()


bt = Backtest(GOOG, SmaCross, commission=.002,
              exclusive_orders=True)
stats = bt.run()
bt.plot()

Results in:

Start                     2004-08-19 00:00:00
End                       2013-03-01 00:00:00
Duration                   3116 days 00:00:00
Exposure Time [%]                       94.27
Equity Final [$]                     68935.12
Equity Peak [$]                      68991.22
Return [%]                             589.35
Buy & Hold Return [%]                  703.46
Return (Ann.) [%]                       25.42
Volatility (Ann.) [%]                   38.43
Sharpe Ratio                             0.66
Sortino Ratio                            1.30
Calmar Ratio                             0.77
Max. Drawdown [%]                      -33.08
Avg. Drawdown [%]                       -5.58
Max. Drawdown Duration      688 days 00:00:00
Avg. Drawdown Duration       41 days 00:00:00
# Trades                                   93
Win Rate [%]                            53.76
Best Trade [%]                          57.12
Worst Trade [%]                        -16.63
Avg. Trade [%]                           1.96
Max. Trade Duration         121 days 00:00:00
Avg. Trade Duration          32 days 00:00:00
Profit Factor                            2.13
Expectancy [%]                           6.91
SQN                                      1.78
_strategy              SmaCross(n1=10, n2=20)
_equity_curve                          Equ...
_trades                       Size  EntryB...
dtype: object

plot of trading simulation

Find more usage examples in the documentation.

Features

  • Simple, well-documented API
  • Blazing fast execution
  • Built-in optimizer
  • Library of composable base strategies and utilities
  • Indicator-library-agnostic
  • Supports any financial instrument with candlestick data
  • Detailed results
  • Interactive visualizations

Alternatives

See alternatives.md for a list of alternative Python backtesting frameworks and related packages.

Comments
  • Order/Trade/Position API

    Order/Trade/Position API

    refs https://github.com/kernc/backtesting.py/issues/8 fixes https://github.com/kernc/backtesting.py/issues/28 closes https://github.com/kernc/backtesting.py/issues/77 closes https://github.com/kernc/backtesting.py/issues/83 closes https://github.com/kernc/backtesting.py/issues/92

    New features:

    • Add order sizing, pyramiding, order types (stop-limit order) (fixes https://github.com/kernc/backtesting.py/issues/3, fixes https://github.com/kernc/backtesting.py/issues/10, closes https://github.com/kernc/backtesting.py/pull/24, closes https://github.com/kernc/backtesting.py/issues/76, closes https://github.com/kernc/backtesting.py/issues/106),
    • Strategy.orders list of active orders,
    • Strategy.trades list of active trades,
    • Strategy.closed_trades list of settled trades,
    • Order class, representing an active order,
    • Trade class, representing an active or already settled trade,
    • auto-downsample plots when too much data (fixes https://github.com/kernc/backtesting.py/issues/35),
    • New stats keys (fixes https://github.com/kernc/backtesting.py/issues/4, fixes https://github.com/kernc/backtesting.py/issues/29):
      • _equity_curve, equity, drawdown, and drawdown duration,
      • _trades, DataFrame of executed trades.

    Breakages:

    • Need to call explicit Strategy.position.close() between subsequent Strategy.buy/sell() calls to approximate previous behavior.
    • Strategy.buy(price=)Strategy.buy(limit=)
    • Strategy.buy/sell() only takes keyword parameters.
    • Position.open_priceTrade.entry_price
    • Position.open_timeTrade.entry_time
    • Strategy.ordersStrategy.orders[i]
    • Backtest.plot(omit_missing=) is gone.
    • Revised backtesting.lib.SignalStrategy.
    opened by kernc 26
  • BrokenProcessPool when doing backtest.optimize

    BrokenProcessPool when doing backtest.optimize

    Expected Behavior

    Returning the stats on the best run for backtest.run()

    Actual Behavior

    raised a BrokenProcessPool error : A process in the process pool was terminated abruptly while the future was running or pending.

    Steps to Reproduce

    bt = Backtest(df, Strat,, cash=starting_money, commission=.002) bt.run() stats = bt.optimize(tresholdVol=range(5,100, 5), maximize='Equity Final [$]')

    Additional info

    • Backtesting version:
    bug help wanted 
    opened by sacharbit 17
  • Optimization memory leak

    Optimization memory leak

    When running an Optimize routine, it appears as though the child processes that are created aren't releasing memory after they finish. If I write a simple routine that just evaluates multiple SMA values for a closing price crossover (doesn't really matter what) the routine will gradually consume all memory available on the machine and then produces a "raised a BrokenProcessPool error : A process in the process pool was terminated abruptly while the future was running or pending." error. I am currently running in AWS on an Ubuntu box.

    Thanks in advance.

    Beginning of the routine: image

    After 5 minutes: image

    • Backtesting version: Backtesting 0.1.2
    bug 
    opened by sumobull 15
  • Using a dict to store dynamic indicators

    Using a dict to store dynamic indicators

    Expected Behavior

    Calculate the strategy and display the result:

    Start                     2004-08-19 00:00:00
    End                       2013-03-01 00:00:00
    Duration                   3116 days 00:00:00
    Exposure [%]                          94.2875
    Equity Final [$]                      69665.1
    Equity Peak [$]                       69722.1
    Return [%]                            596.651
    Buy & Hold Return [%]                 703.458
    Max. Drawdown [%]                    -33.6059
    Avg. Drawdown [%]                    -5.62833
    Max. Drawdown Duration      689 days 00:00:00
    Avg. Drawdown Duration       41 days 00:00:00
    # Trades                                   93
    Win Rate [%]                          53.7634
    Best Trade [%]                        56.9786
    Worst Trade [%]                      -17.0259
    Avg. Trade [%]                        2.44454
    Max. Trade Duration         121 days 00:00:00
    Avg. Trade Duration          32 days 00:00:00
    Expectancy [%]                        6.91837
    SQN                                   1.77227
    Sharpe Ratio                         0.220629
    Sortino Ratio                        0.541607
    Calmar Ratio                        0.0727415
    _strategy                            SmaCross
    dtype: object
    

    Actual Behavior

    No trades were made.

    Start                     2004-08-19 00:00:00
    End                       2013-03-01 00:00:00
    Duration                   3116 days 00:00:00
    Exposure [%]                                0
    Equity Final [$]                        10000
    Equity Peak [$]                         10000
    Return [%]                                  0
    Buy & Hold Return [%]                 703.458
    Max. Drawdown [%]                          -0
    Avg. Drawdown [%]                         NaN
    Max. Drawdown Duration                    NaN
    Avg. Drawdown Duration                    NaN
    # Trades                                    0
    Win Rate [%]                              NaN
    Best Trade [%]                            NaN
    Worst Trade [%]                           NaN
    Avg. Trade [%]                            NaN
    Max. Trade Duration                       NaT
    Avg. Trade Duration                       NaT
    Expectancy [%]                            NaN
    SQN                                       NaN
    Sharpe Ratio                              NaN
    Sortino Ratio                             NaN
    Calmar Ratio                              NaN
    _strategy                            SmaCross
    dtype: object
    

    Steps to Reproduce

    1. Using the code
    from backtesting.test import GOOG
    from backtesting.test import SMA
    from backtesting import Strategy
    from backtesting.lib import crossover
    from backtesting import Backtest
    
    
    class SmaCross(Strategy):
        # Define the two MA lags as *class variables*
        # for later optimization
        n1 = 10
        n2 = 20
    
        def init(self):
            # Precompute two moving averages
            self.sma1 = self.I(SMA, self.data.Close, self.n1)
            self.sma2 = self.I(SMA, self.data.Close, self.n2)
    
        def next(self):
            # If sma1 crosses above sma2, buy the asset
            # Values ok here!
            print(self.data.index[-1],self.sma1[-1])
            if crossover(self.sma1, self.sma2):
                self.buy()
    
            # Else, if sma1 crosses below sma2, sell it
            elif crossover(self.sma2, self.sma1):
                self.sell()
    
    
    bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002)
    print(bt.run())
    
    1. Change the code to
    from backtesting.test import GOOG
    from backtesting.test import SMA
    from backtesting import Strategy
    from backtesting.lib import crossover
    from backtesting import Backtest
    
    
    class SmaCross(Strategy):
        # Define the two MA lags as *class variables*
        # for later optimization
        n1 = 10
        n2 = 20
        timeseries = {}
    
        def init(self):
            # Precompute two moving averages
            self.timeseries["sma1"] = self.I(SMA, self.data.Close, self.n1)
            self.timeseries["sma2"] = self.I(SMA, self.data.Close, self.n2)
    
        def next(self):
            # If sma1 crosses above sma2, buy the asset
            # Is showing the same value!
            print(self.data.index[-1], self.timeseries["sma1"][-1])
            if crossover(self.timeseries["sma1"], self.timeseries["sma2"]):
                self.buy()
    
            # Else, if sma1 crosses below sma2, sell it
            elif crossover(self.timeseries["sma2"], self.timeseries["sma1"]):
                self.sell()
    
    
    bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002)
    print(bt.run())
    

    Additional info

    • Backtesting version: 0.1.7
    • I'm working on a dynamic strategy feature, that's why i need use the dicts, how could i fix this?

    Thanks

    question 
    opened by wesleywilian 14
  • Some questions about take profit (tp) & stop loss(sl)

    Some questions about take profit (tp) & stop loss(sl)

    When I'm using the buy function with a stop loss / take profit argument I would expect that the order can only be closed on that specific stop loss / take profit price. But when I look at the chart (stop loss=0.66%, take profit=1%), I see that the average negative trade result is around -0.80% with as max -1.14752%. How is this possible?

    Also why isn't it possible to optimize the take profit / stop loss rate by using the "optimize" function? Can this be added in a later release? Or can I help contribute?

    Backtesting version: 0.1.7

    invalid 
    opened by AlgoQ 13
  • New Sell order followed by Stop Loss  - double `_close_position` bug

    New Sell order followed by Stop Loss - double `_close_position` bug

    It's a rare situation - you need both stop loss and new order to happen in the same time step - but when happens it is such a pain to nail down what's really wrong, since the framework won't break. Instead, all the data following will be slightly wrong. Started debugging this when trying to understand why ohlc_trades plot would draw two trades going on at the same time, if clearly existing positions need to be closed before opening new ones. That "double trade" plotting is result of that misaligned data due to the erroneous open position and instant stop loss.

    image

    Expected Behavior

    Close the original position, ignore the stop loss and create a new order

    Actual Behavior

    Close the original position, create a new order, and instantly close it due to stop loss

    Steps to Reproduce

    1. Create a strategy that will produce a new sell order the same time step in which a stop loss threshold is reached.

    Additional info

    A couple of fix suggestions:

    1. Update open, high, low once self._open_position(entry, is_long) is called (backtesting.py line 539)

    backtesting.py line 532

    if entry or orders._close:
        self._close_position()
        orders._close = False
    
    ################ THIS
    # First make the entry order, if hit
    if entry:
        if entry is _MARKET_PRICE or high > orders._entry > low:
            self._open_position(entry, is_long)
    ################ THIS
    
    # Check if stop-loss threshold was hit
    if sl and self._position:
        price = (sl if low <= sl <= high else              # hit
                 open if (is_long and open < sl or         # gapped hit
                          not is_long and open > sl) else
                 None)                                     # not hit
        if price is not None:
            self._close_position(price)
            self.orders.cancel()
    
    # Check if take-profit threshold was hit
    if tp and self._position:
        price = (tp if low < tp < high else
                 open if (is_long and open > tp or
                          not is_long and open > sl) else
                 None)
        if price is not None:
            self._close_position(price)
            self.orders.cancel()
       
    ###########################################
    #### SHOULD PROBABLY BE MOVED HERE #######
    ###########################################
    
    
    bug 
    opened by Voyz 13
  • AssertionError while using TrailingStrategy

    AssertionError while using TrailingStrategy

    I am seeing this error when try to run backtest with 10000 days worth data. It doesn't happen for all the stocks, but some. e.g AAPL, AMZN.

    Index: 1946  Close: 35.88999938964844  ATR: 1.6433353276970901
    Index: 1947  Close: 35.779998779296875  ATR: 1.5673829350797757
    Index: 1948  Close: 35.779998779296875  ATR: 1.5232842084983242
    Index: 1949  Close: 36.029998779296875  ATR: 1.4951925556138177
    Index: 1950  Close: 36.13999938964844  ATR: 1.438393141851363
    Index: 1951  Close: 35.65999984741211  ATR: 1.3906505347952947
    Traceback (most recent call last):
      File "backtesting_usa.py", line 116, in <module>
        output = bt.run()
      File "/usr/local/lib/python3.8/site-packages/backtesting/backtesting.py", line 1165, in run
        strategy.next()
      File "backtesting_usa.py", line 83, in next
        super().next()
      File "/usr/local/lib/python3.8/site-packages/backtesting/lib.py", line 446, in next
        trade.sl = max(trade.sl or -np.inf,
      File "/usr/local/lib/python3.8/site-packages/backtesting/backtesting.py", line 633, in sl
        self.__set_contingent('sl', price)
      File "/usr/local/lib/python3.8/site-packages/backtesting/backtesting.py", line 652, in __set_contingent
        assert price is None or 0 < price < np.inf
    AssertionError
    

    I tried printing the index values for each iteration and I found that for this dataset, it processed till index 1951 and failed on 1952.

    If I reduce the backtesting period to 5000, it works because it doesn't come across that piece of data (index 1952). I checked that data and verified the close price, and calculated ATR(14).

    Below is the code snippet:

    class Breakout(TrailingStrategy):
    	timeperiod = 10
    	position_size_decimal = 0.2
    	min_close = []
    	max_close = []
    	def init(self):
    		super().init()
    		self.ema20 = self.I(EMA,self.data.Close,20,overlay=True)
    		self.atr14 = self.I(ATR,self.data.High,self.data.Low,self.data.Close,14)
    		self.set_atr_periods(14)
    		self.set_trailing_sl(1)
    		print(type(self.data.Close))
    		self.min_close, self.max_close = MINMAX(self.data.Close, timeperiod=self.timeperiod)
    
    	def next(self):
    		super().next()
    		index = len(self.data)-1
    		print('Index: '+f'{index} '+' Close: '+f'{self.data.Close[index]} '+' ATR: '+f'{self.atr14[index]}')
    		if not self.position.is_long and self.min_close[index] > (self.max_close[index] * 0.98) and self.max_close[index] < (self.min_close[index] * 1.02):
    			#print('BUY ZONE:'+ f'{self.data.index[index]} {self.rsi2[index]} {self.rsi5[index]} {self.ema200[index]} {self.data.Close[index]}')
    			self.buy(size=self.position_size_decimal)
    

    I download data using yfinance python api. Any help will be greatly appreciated.

    Originally posted by @zlpatel in https://github.com/kernc/backtesting.py/discussions/315

    bug 
    opened by zlpatel 12
  • Open trade at fixed price

    Open trade at fixed price

    Expected Behavior

    I would expect that the trade would open the 'longLine' and close on the 'sma' line (in case of a long trade), but the trade opens/closes always on the close price. Is there a way to make sure that the the trade opens on the long/short line and closes on the sma line. These lines are based on the previous candle close value, so these are fixed values.

    Code

    def next(self):
        if not self.position:
            longLine = self.sma[-2] - (self.sma[-2] * (self.longLineParam / 100))
            shortLine = self.sma[-2] + (self.sma[-2] * (self.shortLineParam / 100))
            if self.data.Close[-1] <= longLine:
                self.buy()
            elif self.data.Close[-1] >= shortLine:
                self.sell()
        
        elif self.position:
            if cross(self.data.Close, self.sma[-2]):
                self.position.close()
    

    Additional info

    Example: image

    question 
    opened by AlgoQ 12
  • Add Order.tag for tracking orders and trades

    Add Order.tag for tracking orders and trades

    Fixes https://github.com/kernc/backtesting.py/issues/197 Fixes https://github.com/kernc/backtesting.py/issues/53

    Hello again. Many apologies for my butchering of the PR process. Git is certainly something I am no expert at. :cry:

    This is another PR which implements the tagging feature, makes tags an object type and fixes previous pep8 issues.

    I am currently testing this for the first time on my own trading platform. The code seems to work fine to persist tags at the moment, but there are far too many trades being taken out, so unless that's a likely outcome of these changes, that problem is likely with my code outside of backtesting.py.

    opened by qacollective 10
  • Equity Plot legend shows original 100% investment which may cause confusion

    Equity Plot legend shows original 100% investment which may cause confusion

    Expected Behavior

    Note this is nitpicky but I noticed a few people misunderstanding the legend when I showed it to them so only a focus group of a few, take the following with a grain of salt.

    The plot legend shows the peak and final equity value as a % but it doesn't subtract the original 100% from the result. This gives the impression that the peak and final "Return" of the strategy was 100% higher than it actually was. I fully recognize that the use of the word "return" was inferred by my brain, not by the chart, but I don't think I'm alone in keying in on the % sign as an indicator of returns.

    This would be fine if it were a "growth of $100" type of chart and used a $ instead of a % However because it is labeled in % (which would be my preference) then perhaps consider subtracting 100% from the legend Peak % and Final %. I think it is perfectly reasonable to show the y-axis starting at 100% but if you wanted to get technical, you could change it to a 0% starting point so everything lined up.

    Actual Behavior

    The chart shows the percent of the original investment, for example in the image below, the "Peak (690%)" and "Final (689%)" imply 690% and 689% of the original invested amount, not a 690% /689% return. The actual returns would have only been 590%/589% over that time period.

    image

    Again, this was just something someone else pointed out to me as a bother and I think tend to agree. It is certainly not a priority or even consensus.

    enhancement good first issue 
    opened by eervin123 10
  • assumption that all callables define __name__ attribute:

    assumption that all callables define __name__ attribute:

    @kernc I am working an issue where I need to use the abstract module of talib

    from backtesting import Backtest, Strategy
    from backtesting.lib import crossover
    
    from backtesting.test import GOOG
    from talib import abstract
    
    
    class SmaCross(Strategy):
        def init(self):
            Ind = abstract.Function('sma')
            inputs = {
                'open': self.data.Open,
                'high': self.data.High,
                'low': self.data.Low,
                'close': self.data.Close,
                'volume': self.data.Volume
            }
            self.ma1 = self.I(Ind, inputs, 10)
            self.ma2 = self.I(Ind, inputs, 20)
    
        def next(self):
            if crossover(self.ma1, self.ma2):
                self.buy()
            elif crossover(self.ma2, self.ma1):
                self.sell()
    
    
    bt = Backtest(GOOG, SmaCross,
                  cash=10000, commission=.002)
    print(bt.run())
    bt.plot()
    

    I need this feature as I am dynamically getting Indicators using just function name

    Running this ends up in this error

    Traceback (most recent call last):
      File "/home/skywalker/PycharmProjects/backtester/backtest.py", line 30, in <module>
        print(bt.run())
      File "/home/skywalker/PycharmProjects/backtester/venv/lib/python3.8/site-packages/backtesting/backtesting.py", line 692, in run
        strategy.init()
      File "/home/skywalker/PycharmProjects/backtester/backtest.py", line 18, in init
        self.ma1 = self.I(Ind, inputs, 10)
      File "/home/skywalker/PycharmProjects/backtester/venv/lib/python3.8/site-packages/backtesting/backtesting.py", line 118, in I
        func_name = _as_str(func)
      File "/home/skywalker/PycharmProjects/backtester/venv/lib/python3.8/site-packages/backtesting/_util.py", line 24, in _as_str
        name = value.__name__.replace('<lambda>', 'λ')
    AttributeError: 'Function' object has no attribute '__name__'
    
    

    The issue seems to be way the function is constructed later in the backtesting.py

            if name is None:
                params = ','.join(filter(None, map(_as_str, chain(args, kwargs.values()))))
                func_name = _as_str(func)
                name = ('{}({})' if params else '{}').format(func_name, params)
    

    Any tips on how to get around it?

    • Backtesting version: latest master
    bug 
    opened by arunavo4 10
  • Backtest plotting results in empty graph

    Backtest plotting results in empty graph

    Expected Behavior

    Graph should not be empty

    Actual Behavior

    There are also tons of warnings in the console...

    /Users/lennard/opt/miniconda3/envs/torch/lib/python3.9/site-packages/backtesting/_plotting.py:122: UserWarning:
    
    Data contains too many candlesticks to plot; downsampling to '1H'. See `Backtest.plot(resample=...)`
    
    /Users/lennard/opt/miniconda3/envs/torch/lib/python3.9/site-packages/backtesting/_plotting.py:148: FutureWarning:
    
    Passing method to Int64Index.get_loc is deprecated and will raise in a future version. Use index.get_indexer([item], method=...) instead.
    
    BokehDeprecationWarning: Passing lists of formats for DatetimeTickFormatter scales was deprecated in Bokeh 3.0. Configure a single string format for each scale
    /Users/lennard/opt/miniconda3/envs/torch/lib/python3.9/site-packages/bokeh/models/formatters.py:399: UserWarning:
    
    DatetimeFormatter scales now only accept a single format. Using the first prodvided: '%d %b' 
    
    BokehDeprecationWarning: Passing lists of formats for DatetimeTickFormatter scales was deprecated in Bokeh 3.0. Configure a single string format for each scale
    /Users/lennard/opt/miniconda3/envs/torch/lib/python3.9/site-packages/bokeh/models/formatters.py:399: UserWarning:
    
    DatetimeFormatter scales now only accept a single format. Using the first prodvided: '%m/%Y' 
    

    Steps to Reproduce

    ... doesn't matter ...
    
    bt = Backtest(dfpl, MyStrategy, cash=100, margin=1/10, commission=.0)
    
    # backtesting.set_bokeh_output(notebook=False) - doesn't matter
    
    bt.plot(show_legend=False)
    

    Additional info

    Bildschirm­foto 2022-12-13 um 17 49 54
    • Backtesting version: 0.3.3
    • bokeh.__version__: 3.0.2
    • Using Jupyter Notebook
    • OS: MacOS Ventura 13.0 (Apple Silicon)

    yes there are stats if I run the strategy:

    Start                     2020-01-01 22:00:00
    End                       2020-12-31 21:55:00
    Duration                    364 days 23:55:00
    Exposure Time [%]                    4.700638
    Equity Final [$]                   139.304018
    Equity Peak [$]                    139.533855
    Return [%]                          39.304018
    Buy & Hold Return [%]                8.932887
    Return (Ann.) [%]                   30.478123
    Volatility (Ann.) [%]               17.919015
    Sharpe Ratio                         1.700882
    Sortino Ratio                        3.531841
    Calmar Ratio                         4.042517
    Max. Drawdown [%]                   -7.539393
    Avg. Drawdown [%]                   -0.858869
    Max. Drawdown Duration       55 days 19:15:00
    Avg. Drawdown Duration        3 days 12:49:00
    # Trades                                  748
    Win Rate [%]                        49.465241
    Best Trade [%]                       0.222753
    Worst Trade [%]                     -0.230765
    Avg. Trade [%]                        0.00461
    Max. Trade Duration           2 days 00:45:00
    Avg. Trade Duration           0 days 00:27:00
    Profit Factor                        1.269556
    Expectancy [%]                       0.004625
    SQN                                  2.384091
    _strategy                          MyStrategy
    _equity_curve                             ...
    _trades                        Size  Entry...
    dtype: object
    
    duplicate 
    opened by trueToastedCode 8
  • ENH: Add Backtest(spread=), change Backtest(commission=)

    ENH: Add Backtest(spread=), change Backtest(commission=)

    commission= is now applied twice as common with brokers. spread= takes the role commission= had previously.

    Fixes https://github.com/kernc/backtesting.py/issues/149 Fixes https://github.com/kernc/backtesting.py/issues/113 Closes https://github.com/kernc/backtesting.py/pull/662

    opened by kernc 0
  • error in example doc with scikit-optimize

    error in example doc with scikit-optimize

    The example doc "Parameter Heatmap", section "Model-based optimization". https://kernc.github.io/backtesting.py/doc/examples/Parameter%20Heatmap%20&%20Optimization.html

    When I run this snippet I get an error.

    stats_skopt, heatmap, optimize_result = backtest.optimize(
         n1=[10, 100], # Note: For method="skopt", we
         n2=[20, 200], # only need interval end-points
         n_enter=[10, 40],
         n_exit=[10, 30],
         constraint=lambda p: p.n_exit < p.n_enter < p.n1 < p.n2,
         maximize='Equity Final [$]',
         method='skopt',
         max_tries=200,
         random_state=0,
         return_heatmap=True,
         return_optimization=True)
    
    ---------------------------------------------------------------------------
    InvalidParameterError                     Traceback (most recent call last)
    File <timed exec>:1, in <module>
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/backtesting/backtesting.py:1490, in Backtest.optimize(self, maximize, method, max_tries, constraint, return_heatmap, return_optimization, random_state, **kwargs)
       1488     output = _optimize_grid()
       1489 elif method == 'skopt':
    -> 1490     output = _optimize_skopt()
       1491 else:
       1492     raise ValueError(f"Method should be 'grid' or 'skopt', not {method!r}")
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/backtesting/backtesting.py:1456, in Backtest.optimize.<locals>._optimize_skopt()
       1452 with warnings.catch_warnings():
       1453     warnings.filterwarnings(
       1454         'ignore', 'The objective has been evaluated at this point before.')
    -> 1456     res = forest_minimize(
       1457         func=objective_function,
       1458         dimensions=dimensions,
       1459         n_calls=max_tries,
       1460         base_estimator=ExtraTreesRegressor(n_estimators=20, min_samples_leaf=2),
       1461         acq_func='LCB',
       1462         kappa=3,
       1463         n_initial_points=min(max_tries, 20 + 3 * len(kwargs)),
       1464         initial_point_generator='lhs',  # 'sobel' requires n_initial_points ~ 2**N
       1465         callback=DeltaXStopper(9e-7),
       1466         random_state=random_state)
       1468 stats = self.run(**dict(zip(kwargs.keys(), res.x)))
       1469 output = [stats]
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/skopt/optimizer/forest.py:186, in forest_minimize(func, dimensions, base_estimator, n_calls, n_random_starts, n_initial_points, acq_func, initial_point_generator, x0, y0, random_state, verbose, callback, n_points, xi, kappa, n_jobs, model_queue_size)
         10 def forest_minimize(func, dimensions, base_estimator="ET", n_calls=100,
         11                     n_random_starts=None, n_initial_points=10, acq_func="EI",
         12                     initial_point_generator="random",
         13                     x0=None, y0=None, random_state=None, verbose=False,
         14                     callback=None, n_points=10000, xi=0.01, kappa=1.96,
         15                     n_jobs=1, model_queue_size=None):
         16     """Sequential optimisation using decision trees.
         17 
         18     A tree based regression model is used to model the expensive to evaluate
       (...)
        184         :class:`skopt.dummy_minimize`, :class:`skopt.gbrt_minimize`
        185     """
    --> 186     return base_minimize(func, dimensions, base_estimator,
        187                          n_calls=n_calls, n_points=n_points,
        188                          n_random_starts=n_random_starts,
        189                          n_initial_points=n_initial_points,
        190                          initial_point_generator=initial_point_generator,
        191                          x0=x0, y0=y0, random_state=random_state,
        192                          n_jobs=n_jobs,
        193                          acq_func=acq_func,
        194                          xi=xi, kappa=kappa, verbose=verbose,
        195                          callback=callback, acq_optimizer="sampling",
        196                          model_queue_size=model_queue_size)
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/skopt/optimizer/base.py:300, in base_minimize(func, dimensions, base_estimator, n_calls, n_random_starts, n_initial_points, initial_point_generator, acq_func, acq_optimizer, x0, y0, random_state, verbose, callback, n_points, n_restarts_optimizer, xi, kappa, n_jobs, model_queue_size)
        298 next_x = optimizer.ask()
        299 next_y = func(next_x)
    --> 300 result = optimizer.tell(next_x, next_y)
        301 result.specs = specs
        302 if eval_callbacks(callbacks, result):
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/skopt/optimizer/optimizer.py:493, in Optimizer.tell(self, x, y, fit)
        490         y = list(y)
        491         y[1] = log(y[1])
    --> 493 return self._tell(x, y, fit=fit)
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/skopt/optimizer/optimizer.py:536, in Optimizer._tell(self, x, y, fit)
        534 with warnings.catch_warnings():
        535     warnings.simplefilter("ignore")
    --> 536     est.fit(self.space.transform(self.Xi), self.yi)
        538 if hasattr(self, "next_xs_") and self.acq_func == "gp_hedge":
        539     self.gains_ -= est.predict(np.vstack(self.next_xs_))
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/sklearn/ensemble/_forest.py:341, in BaseForest.fit(self, X, y, sample_weight)
        314 def fit(self, X, y, sample_weight=None):
        315     """
        316     Build a forest of trees from the training set (X, y).
        317 
       (...)
        339         Fitted estimator.
        340     """
    --> 341     self._validate_params()
        343     # Validate or convert input data
        344     if issparse(y):
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/sklearn/base.py:570, in BaseEstimator._validate_params(self)
        562 def _validate_params(self):
        563     """Validate types and values of constructor parameters
        564 
        565     The expected type and values must be defined in the `_parameter_constraints`
       (...)
        568     accepted constraints.
        569     """
    --> 570     validate_parameter_constraints(
        571         self._parameter_constraints,
        572         self.get_params(deep=False),
        573         caller_name=self.__class__.__name__,
        574     )
    
    File ~/anaconda3/envs/trade/lib/python3.8/site-packages/sklearn/utils/_param_validation.py:97, in validate_parameter_constraints(parameter_constraints, params, caller_name)
         91 else:
         92     constraints_str = (
         93         f"{', '.join([str(c) for c in constraints[:-1]])} or"
         94         f" {constraints[-1]}"
         95     )
    ---> 97 raise InvalidParameterError(
         98     f"The {param_name!r} parameter of {caller_name} must be"
         99     f" {constraints_str}. Got {param_val!r} instead."
        100 )
    
    InvalidParameterError: The 'criterion' parameter of ExtraTreesRegressor must be a str among {'squared_error', 'absolute_error', 'poisson', 'friedman_mse'}. Got 'mse' instead.
    
    • Backtesting version: 0.3.3
    • bokeh.__version__: Python 3.11, bokeh 2.4.3; Python 3.8.11, bokeh 3.0.3;
    • scikit-optimize 0.9.0
    • OS: Ubuntu 20.04
    opened by vladiscripts 3
  • Add support for tick data

    Add support for tick data

    Very concretely, I wish for there to be added support for tick data in the backtest.

    One example of how it could work:

    • Your backtest is on the 5m timeframe
    • You can additionally pass a 1m timeframe or even down to 1 second data

    Benefits:

    • More accurate results when backtesting, more accurate trigger on stop loss and take profits
    • More flexibility in your strategies, e.g. you can implement an approach to check if the tick distribution fits your assumptions

    Cons:

    • Could possibly be slower because of increased size of data, but this can be resolved with making it optional and keep the normal functionality just as fast as always
    opened by casperbh96 1
  • Plotting does not work with Bokeh v3.0

    Plotting does not work with Bokeh v3.0

    Expected Behavior

    I installed the tool and all required libraries on Ubuntu 22.04, Python 3.11.0 and ta-lib (compiled from latest source code 0.4.0). The backtest from the example code on home page was run. The test itself ran without

    problems but the plot html does not work. It should show the graph.

    Actual Behavior

    Please see the attached. The html layout is not working. Strangely, no JS error from browser console.

    Steps to Reproduce

    1. Install backtesting.py
    2. Run the example code on the project home page
    3. Open the plot in IE Edge

    Additional info

    Screenshot 2022-11-08 120227 Screenshot 2022-11-08 120313

    • Backtesting version: 0.3.3
    bug upstream 
    opened by aicheung 5
  • Can't run it over 10001 index from csv , then I got the error  f

    Can't run it over 10001 index from csv , then I got the error f"Length of passed values is {len(data)}, " ValueError: Length of passed values is 2, index implies 1.

    Expected Behavior

    I wanna use it over 10000 index with this command(index_col='time', parse_dates=True) without the error ,when I import the csv.

    Of cource I should type the code like follow. df = pd.read_csv("df.csv")

    But the date didn't recognize ,when I don't use index_col='time', parse_dates=True. And the follow error occurred ,when I import the csvfile over 10000 index.There is reproducibility.

    Actual Behavior

    I read the csv by follow code and csv

    code filename = "raw.csv" df = pd.read_csv(filename,index_col='time', parse_dates=True)

    csvdata example actual data have 10002 index. time | Open | High | Low | Close | Volume -- | -- | -- | -- | -- | -- 2020/1/1 0:01 | 7187.67 | 7188.06 | 7182.2 | 7184.03 | 7.248148 2020/1/1 0:02 | 7184.41 | 7184.71 | 7180.26 | 7182.43 | 11.68168 2020/1/1 0:03 | 7183.83 | 7188.94 | 7182.49 | 7185.94 | 10.02539

    Line 10001 does not give an error. But an error occurs when the csv reaches 10002 rows. This error occurred when reading CSV with the following command.

    target code index_col='time', parse_dates=True

    Steps to Reproduce

    
    C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\backtesting\_plotting.py:122: UserWarning: Data contains too many candlesticks to plot; downsampling to '1T'. See `Backtest.plot(resample=...)`
      warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
    Traceback (most recent call last):
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\groupby\generic.py", line 261, in aggregate
        func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\groupby\groupby.py", line 1085, in _python_agg_general
        result, counts = self.grouper.agg_series(obj, f)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\groupby\ops.py", line 904, in agg_series
        return grouper.get_result()
      File "pandas\_libs\reduction.pyx", line 164, in pandas._libs.reduction.SeriesBinGrouper.get_result
      File "pandas\_libs\reduction.pyx", line 76, in pandas._libs.reduction._BaseGrouper._apply_to_group
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\groupby\groupby.py", line 1062, in <lambda>
        f = lambda x: func(x, *args, **kwargs)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\backtesting\_plotting.py", line 147, in f
        mean_time = int(bars.loc[s.index].view(int).mean())
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\series.py", line 668, in view
        self._values.view(dtype), index=self.index
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\series.py", line 314, in __init__
        f"Length of passed values is {len(data)}, "
    ValueError: Length of passed values is 2, index implies 1.
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "1.py", line 183, in <module>
        bt.plot() 
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\backtesting\backtesting.py", line 1609, in plot
        open_browser=open_browser)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\backtesting\_plotting.py", line 204, in plot
        resample, df, indicators, equity_data, trades)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\backtesting\_plotting.py", line 158, in _maybe_resample_data
        ExitBar=_group_trades('ExitTime'),
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\resample.py", line 288, in aggregate
        result, how = self._aggregate(func, *args, **kwargs)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\base.py", line 416, in _aggregate
        result = _agg(arg, _agg_1dim)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\base.py", line 383, in _agg
        result[fname] = func(fname, agg_how)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\base.py", line 367, in _agg_1dim
        return colg.aggregate(how)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\groupby\generic.py", line 267, in aggregate
        result = self._aggregate_named(func, *args, **kwargs)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\groupby\generic.py", line 480, in _aggregate_named
        output = func(group, *args, **kwargs)
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\backtesting\_plotting.py", line 147, in f
        mean_time = int(bars.loc[s.index].view(int).mean())
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\series.py", line 668, in view
        self._values.view(dtype), index=self.index
      File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\series.py", line 314, in __init__
        f"Length of passed values is {len(data)}, "
    ValueError: Length of passed values is 2, index implies 1.
    

    Additional info

    • Backtesting version: 0.3.3
    • My python 3.6.8
    • win10
    opened by KnudY9 2
Python Backtesting library for trading strategies

backtrader Yahoo API Note: [2018-11-16] After some testing it would seem that data downloads can be again relied upon over the web interface (or API v

DRo 9.8k Dec 30, 2022
An Algorithmic Trading Library for Crypto-Assets in Python

Service Master Develop CI Badge Catalyst is an algorithmic trading library for crypto-assets written in Python. It allows trading strategies to be eas

Enigma 2.4k Jan 5, 2023
Python Algorithmic Trading Library

PyAlgoTrade PyAlgoTrade is an event driven algorithmic trading Python library. Although the initial focus was on backtesting, paper trading is now pos

Gabriel Becedillas 3.9k Jan 1, 2023
Zipline, a Pythonic Algorithmic Trading Library

Zipline is a Pythonic algorithmic trading library. It is an event-driven system for backtesting. Zipline is currently used in production as the backte

Quantopian, Inc. 15.7k Jan 2, 2023
An open source reinforcement learning framework for training, evaluating, and deploying robust trading agents.

TensorTrade: Trade Efficiently with Reinforcement Learning TensorTrade is still in Beta, meaning it should be used very cautiously if used in producti

null 4k Dec 30, 2022
Github.com/CryptoSignal - #1 Quant Trading & Technical Analysis Bot - 2,100 + stars, 580 + forks

CryptoSignal - #1 Quant Trading & Technical Analysis Bot - 2,100 + stars, 580 + forks https://github.com/CryptoSignal/Crypto-Signal Development state:

Github.com/Signal - 2,100 + stars, 580 + forks 4.2k Jan 1, 2023
A python wrapper for Alpha Vantage API for financial data.

alpha_vantage Python module to get stock data/cryptocurrencies from the Alpha Vantage API Alpha Vantage delivers a free API for real time financial da

Romel Torres 3.8k Jan 7, 2023
Portfolio and risk analytics in Python

pyfolio pyfolio is a Python library for performance and risk analysis of financial portfolios developed by Quantopian Inc. It works well with the Zipl

Quantopian, Inc. 4.8k Jan 8, 2023
Python sync/async framework for Interactive Brokers API

Introduction The goal of the IB-insync library is to make working with the Trader Workstation API from Interactive Brokers as easy as possible. The ma

Ewald de Wit 2k Dec 30, 2022
bt - flexible backtesting for Python

bt - Flexible Backtesting for Python bt is currently in alpha stage - if you find a bug, please submit an issue. Read the docs here: http://pmorissett

Philippe Morissette 1.6k Jan 5, 2023
ffn - a financial function library for Python

ffn - Financial Functions for Python Alpha release - please let me know if you find any bugs! If you are looking for a full backtesting framework, ple

Philippe Morissette 1.4k Jan 1, 2023
ARCH models in Python

arch Autoregressive Conditional Heteroskedasticity (ARCH) and other tools for financial econometrics, written in Python (with Cython and/or Numba used

Kevin Sheppard 1k Jan 4, 2023
Q-Fin: A Python library for mathematical finance.

Q-Fin A Python library for mathematical finance. Installation https://pypi.org/project/QFin/ pip install qfin Bond Pricing Option Pricing Black-Schol

Roman Paolucci 247 Jan 1, 2023
personal finance tracker, written in python 3 and using the wxPython GUI toolkit.

personal finance tracker, written in python 3 and using the wxPython GUI toolkit.

wenbin wu 23 Oct 30, 2022
Beibo is a Python library that uses several AI prediction models to predict stocks returns over a defined period of time.

Beibo is a Python library that uses several AI prediction models to predict stocks returns over a defined period of time.

Santosh 54 Dec 10, 2022
Indicator divergence library for python

Indicator divergence library This module aims to help to find bullish/bearish divergences (regular or hidden) between two indicators using argrelextre

null 8 Dec 13, 2022
The Original Snake Game. Maneuver a snake in its burrow and earn points while avoiding the snake itself and the walls of the snake burrow.

Maneuver a snake in its burrow and earn points while avoiding the snake itself and the walls of the snake burrow. The snake grows when it eats an apple by default which can be disabled in the settings tab where you can also find all the other customization options.

null 17 Nov 12, 2022
Cryptocurrency Trading Bot - A trading bot to automate cryptocurrency trading strategies using Python, equipped with a basic GUI

Cryptocurrency Trading Bot - A trading bot to automate cryptocurrency trading strategies using Python, equipped with a basic GUI. Used REST and WebSocket API to connect to two of the most popular crypto exchanges in the world.

Francis 8 Sep 15, 2022