polakowo / vectorbt

Find your trading edge, using the fastest engine for backtesting, algorithmic trading, and research.
https://vectorbt.dev
Other
4.32k stars 617 forks source link

Datetime support with from_order_func #727

Closed Kinzowa closed 1 month ago

Kinzowa commented 3 months ago

Hello,

I use from_order_func to generate a backtest with the code below, but for some reason the X axis of the charts are integers, not datetime. It's a similar problem with indexes of tables orders.records_readable and trades.records_readable.


   Order Id  Column  Timestamp    Size     Price  Fees  Side
0         0       0         15  0.0007  62826.02   0.0   Buy
1         1       0         34  0.0006  62650.01   0.0  Sell
2         2       0         38  0.0006  62626.92   0.0   Buy
3         3       0         66  0.0006  60371.34   0.0  Sell
   Exit Trade Id  Column    Size  Entry Timestamp  Avg Entry Price  \
0              0       0  0.0006               15     62826.020000   
1              1       0  0.0006               15     62655.362857   
2              2       0  0.0001               15     62655.362857   

   Entry Fees  Exit Timestamp  Avg Exit Price  Exit Fees       PnL    Return  \
0         0.0              34        62650.01        0.0 -0.105606 -0.002802   
1         0.0              66        60371.34        0.0 -1.370414 -0.036454   
2         0.0              86        57450.25        0.0 -0.520511 -0.083075   

  Direction  Status  Position Id  
0      Long  Closed            0  
1      Long  Closed            0  
2      Long    Open            0  

With Numba it's not possible to use Pandas dataframe or series in inputs, but arrays of float or integers. Could you please give indications on how to support datetimes ?

Thank you Kinzowa

   def generate(self):
        self.logger.info("Generate backtest")
        backtest = vbt.Portfolio.from_order_func(
            self._close,
            self._order_func_nb,
            vbt.Rep("long_entries"),
            vbt.Rep("long_exits"),
            vbt.Rep("short_entries"),
            vbt.Rep("short_exits"),
            vbt.Rep("weight"),
            vbt.Rep("timestamps"),
            vbt.Rep("open_price_tolerance"),
            vbt.Rep("messages"),
            pre_sim_func_nb=self.pre_sim_func_nb,
            pre_sim_args=(
                vbt.Rep("open"),
                vbt.Rep("high"),
                vbt.Rep("low"),
            ),
            post_order_func_nb=self.post_order_func_nb,
            post_order_args=(
                vbt.Rep("take_profit_enabled"),
                vbt.Rep("take_profit_threshold"),
                vbt.Rep("messages"),
            ),
            broadcast_named_args=dict(  # broadcast against each other
                long_entries=self._long_entries,
                long_exits=self._long_exits,
                short_entries=self._short_entries,
                short_exits=self._short_exits,
                open_price_tolerance=float(self.backtest.open_price_tolerance),
                take_profit_enabled=self.backtest.take_profit_enabled,
                take_profit_threshold=float(self.backtest.take_profit_threshold),
                weight=self._weight,
                timestamps=self._timestamp,
                open=self._open,
                high=self._high,
                low=self._low,
                messages=self.backtest.messages,
                size_type=SizeType.TargetPercent,
            ),
            freq=self._freq_str,
            # callback=self.update_array,
            # callback_args=(self._failed_orders,),
        )
        self.logger.info("Backtest complete")
        return backtest
class BacktestGenerator(BacktestService):
    def __init__(self, backtest_id, signals, candles, freq):
        super().__init__(backtest_id)

        self._candles = candles
        self._signals = signals
        self._freq_str = freq
        self._weight = self._signals["weight"].values

        # Extract prices from OHLCV
        self._open = self._candles["open_price"].values.astype(np.float64)
        self._high = self._candles["high_price"].values.astype(np.float64)
        self._low = self._candles["low_price"].values.astype(np.float64)
        self._close = self._candles["close_price"].values.astype(np.float64)

        # Extract actions from signals
        self._long_entries = self._signals["open_long"].values.astype(np.bool_)
        self._long_exits = self._signals["close_long"].values.astype(np.bool_)
        self._short_entries = self._signals["open_short"].values.astype(np.bool_)
        self._short_exits = self._signals["close_short"].values.astype(np.bool_)

        # Extract weights and create array
        self._weight = np.array([float(d) for d in self._weight])

        # Set datetime indexes
        self._timestamp = np.array([d.timestamp() for d in self._signals.index])
polakowo commented 3 months ago

The arrays that you pass to from_order_func must have a datetime index. There should be no problem passing Pandas DataFrames/Series to from_order_func through broadcast_named_args.

Kinzowa commented 1 month ago

Now it works better, my problem was the object data type in the Pandas Series.