vectorbt icon indicating copy to clipboard operation
vectorbt copied to clipboard

Custom order initialization for all Portfolio class-based methods

Open oxcabe opened this issue 1 year ago • 2 comments

Think of how Portfolio.from_order_func(...)[1] allows to construct orders based on arbitrary, user-defined logic, but extended to all Portfolio.from_.*(...) functions.

This feature request (that, if valid, I do offer myself to implement) comes from the need to specify variable fixed_fees for each order generated inside a Portfolio.from_signals(...) call.

I wasn't able to find a way to either:

  • Modify individual orders inside Portfolio.from_signals(...).
  • Approach signal-to-order functionality in any other scenario.

[1] vectorbt/portfolio/base.py#L3145

oxcabe avatar Apr 25 '23 19:04 oxcabe

There are ways to change any order-related info of a signal.

Provide a field you want to change as an array with one element to from_signals. Also use a template to pass the same array to the signal function. In the signal function, modify the only element of that array to the information you want to use at this row/column (this works thanks to flexible indexing).

Another way is to disable keep_raw for the argument you want to change such that an array of the full shape is build, and then set each element in that array in the signal function (size[c.i, c.col] = current_size)

polakowo avatar Apr 26 '23 17:04 polakowo

Thanks for the quick reply. Although none of these approaches exactly solve my particular issue, I implemented the first in a way that still doesn't work but is a good starting point for discussion.

The signal function I would like to use:

from typing import Tuple

from numba import njit
import numpy as np

from vectorbt._typing import ArrayLike, Optional, Union
from vectorbt.portfolio.enums import Direction, SignalContext
from vectorbt.portfolio.nb import dir_enex_signal_func_nb


@njit
def fees_per_share_signal_func(c: SignalContext,
                           fixed_fees_arr: ArrayLike = np.array(0.0),
                           fixed_fees: float = 0.0,
                           entries: Optional[Union[ArrayLike, bool]] = False,
                           exits: Optional[Union[ArrayLike, bool]] = False,
                           direction: ArrayLike = np.asarray(Direction.LongOnly)) -> Tuple[bool, bool, bool, bool]:
    # Implement some fixed_fees related logic

    return dir_enex_signal_func_nb(c, entries, exits, direction)

.

And this is the code, based on the hyperparameter optimization example[1], that would use the signal function to manipulate fees internally:

import numpy as np
import vectorbt as vbt


symbols = ['AAPL', 'NFLX']

start_date = '2023-01-01 UTC'
end_date   = '2023-04-01 UTC'

timeframe = '1H'

price = vbt.YFData.download(symbols, missing_index='drop',
                            start=start_date, end=end_date,
                            interval=timeframe).get('Close')

windows = np.arange(2, 101)
fast_ma, slow_ma = vbt.MA.run_combs(price, window=windows, r=2, short_names=['fast', 'slow'])
entries = fast_ma.ma_crossed_above(slow_ma)
exits = fast_ma.ma_crossed_below(slow_ma)

entries, exits, _ = vbt.base.reshape_fns.broadcast(entries, exits, price,
                                                   require_kwargs=dict(requirements='W'),
                                                   keep_raw=True)

fixed_fees = 0.2 # $0.20 per share
fixed_fees_arr = np.array(fixed_fees)
signal_args = (fixed_fees_arr, fixed_fees, entries, exits) # IMPORTANT! entries and exits now go here

pf_kwargs = dict(init_cash=100_000,
                 size=np.inf,
                 freq='1H',
                 signal_func_nb=fees_per_share_signal_func,
                 signal_args=signal_args)

pf = vbt.Portfolio.from_signals(price, **pf_kwargs)

.

The most important remarks for this implementation are:

  • Entries and exits are broadcasted before being included into signal_args, instead of just passing them to the from_signal(...) method. This is intended, as entries and exits can't be passed to the latter if a signal_func is specified.

  • This approach wouldn't allow to compute fees based on position sizing, as it is calculated right after the signal function call. It may be of interest, in some cases, to have fees vary based in the asset quantity that is added into a position.

  • I've also noticed that pf.total_return(...) is missing the hyperparameter dimensions for all combinations (I believe this is a consequence of how entries and exits are handled in the example), thus rendering the heatmap useless.

[1] https://vectorbt.dev/getting-started/usage/

oxcabe avatar Apr 27 '23 16:04 oxcabe