Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom order initialization for all Portfolio class-based methods #588

Open
oxcabe opened this issue Apr 25, 2023 · 2 comments
Open

Custom order initialization for all Portfolio class-based methods #588

oxcabe opened this issue Apr 25, 2023 · 2 comments

Comments

@oxcabe
Copy link

oxcabe commented Apr 25, 2023

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

@polakowo
Copy link
Owner

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)

@oxcabe
Copy link
Author

oxcabe commented Apr 27, 2023

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/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants