def from_order_func(cls, main_price, order_func_nb, *args, init_capital=None, row_wise=False, row_prep_func_nb=None, broadcast_kwargs={}, freq=None, **kwargs): """Build portfolio from a custom order function. Starting with initial capital `init_capital`, iterates over shape `main_price.shape`, and for each data point, generates an order using `order_func_nb`. This way, you can specify order size, price and transaction costs dynamically (for example, based on the current balance). if `row_wise` is `True`, see `vectorbt.portfolio.nb.simulate_row_wise_nb`. Otherwise, see `vectorbt.portfolio.nb.simulate_nb`. Args: main_price (pandas_like): Main price of the asset, such as close. Will broadcast. order_func_nb (function): Function that returns an order. See `vectorbt.portfolio.enums.Order`. *args: Arguments passed to `order_func_nb`. init_capital (float or array_like): The initial capital. Will broadcast. Allowed is either a single value or value per column. row_wise (bool): If `True`, iterates over rows, otherwise over columns. Set to `True` if columns depend upon each other. row_prep_func_nb (function): Function to call before iterating over the next row. Can be used to do preprocessing, such as to calculate past returns. broadcast_kwargs: Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`. freq (any): Index frequency in case `main_price.index` is not datetime-like. **kwargs: Keyword arguments passed to the `__init__` method. For defaults, see `vectorbt.defaults.portfolio`. All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`. At the end, they will have the same metadata. !!! note `order_func_nb` must be Numba-compiled. Example: Placing a buy order each day: ```python-repl >>> from vectorbt.portfolio import Order, SizeType >>> @njit ... def order_func_nb(oc, price): ... return Order(10, SizeType.Shares, price[oc.i], ... fees=0.01, fixed_fees=1., slippage=0.01) >>> portfolio = vbt.Portfolio.from_order_func( ... price, order_func_nb, price.values, init_capital=100) >>> portfolio.orders.records col idx size price fees side 0 0 0 10.0 1.01 1.101 0 1 0 1 10.0 2.02 1.202 0 2 0 2 10.0 3.03 1.303 0 3 0 3 10.0 2.02 1.202 0 4 0 4 10.0 1.01 1.101 0 >>> portfolio.equity 2020-01-01 98.799 2020-01-02 107.397 2020-01-03 125.794 2020-01-04 94.392 2020-01-05 53.191 Name: a, dtype: float64 ``` """ # Get defaults if init_capital is None: init_capital = defaults.portfolio['init_capital'] # Perform checks checks.assert_type(main_price, (pd.Series, pd.DataFrame)) checks.assert_numba_func(order_func_nb) # Broadcast inputs # Only main_price is broadcasted, others can remain unchanged thanks to flexible indexing keep_raw = (False, True) main_price, init_capital = reshape_fns.broadcast(main_price, init_capital, **broadcast_kwargs, writeable=True, keep_raw=keep_raw) target_shape = (main_price.shape[0], main_price.shape[1] if main_price.ndim > 1 else 1) # Perform calculation if row_wise: if row_prep_func_nb is None: row_prep_func_nb = nb.none_row_prep_func_nb order_records, cash, shares = nb.simulate_row_wise_nb( target_shape, init_capital, row_prep_func_nb, order_func_nb, *args) else: if row_prep_func_nb is not None: raise ValueError( "Function row_prep_func_nb can be only called when row_wise=True" ) order_records, cash, shares = nb.simulate_nb( target_shape, init_capital, order_func_nb, *args) # Bring to the same meta cash = main_price.vbt.wrap(cash) shares = main_price.vbt.wrap(shares) orders = Orders(order_records, main_price, freq=freq) if checks.is_series(main_price): init_capital = init_capital.item(0) else: init_capital = np.broadcast_to(init_capital, (target_shape[1], )) init_capital = main_price.vbt.wrap_reduced(init_capital) return cls(main_price, init_capital, orders, cash, shares, freq=freq, **kwargs)
def from_signals(cls, main_price, entries, exits, size=np.inf, size_type=SizeType.Shares, entry_price=None, exit_price=None, init_capital=None, fees=None, fixed_fees=None, slippage=None, accumulate=False, broadcast_kwargs={}, freq=None, **kwargs): """Build portfolio from entry and exit signals. For each signal in `entries`, buys `size` of shares for `entry_price` to enter a position. For each signal in `exits`, sells everything for `exit_price` to exit the position. Accumulation of orders is disabled by default. For more details, see `vectorbt.portfolio.nb.simulate_from_signals_nb`. Args: main_price (pandas_like): Main price of the asset, such as close. Will broadcast. entries (array_like): Boolean array of entry signals. Will broadcast. exits (array_like): Boolean array of exit signals. Will broadcast. size (float or array_like): The amount of shares to order. Will broadcast. To buy/sell everything, set the size to `np.inf`. size_type (int or array_like): See `vectorbt.portfolio.enums.SizeType`. Only `SizeType.Shares` and `SizeType.Cash` are supported. entry_price (array_like): Entry price. Defaults to `main_price`. Will broadcast. exit_price (array_like): Exit price. Defaults to `main_price`. Will broadcast. init_capital (float or array_like): The initial capital. Will broadcast. Allowed is either a single value or value per column. fees (float or array_like): Fees in percentage of the order value. Will broadcast. fixed_fees (float or array_like): Fixed amount of fees to pay per order. Will broadcast. slippage (float or array_like): Slippage in percentage of price. Will broadcast. accumulate (bool): If `accumulate` is `True`, entering the market when already in the market will be allowed to increase a position. broadcast_kwargs: Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`. freq (any): Index frequency in case `main_price.index` is not datetime-like. **kwargs: Keyword arguments passed to the `__init__` method. For defaults, see `vectorbt.defaults.portfolio`. All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`. At the end, they will have the same metadata. Example: Portfolio from various signal sequences: ```python-repl >>> entries = pd.DataFrame({ ... 'a': [True, False, False, False, False], ... 'b': [True, False, True, False, True], ... 'c': [True, True, True, True, True] ... }, index=index) >>> exits = pd.DataFrame({ ... 'a': [False, False, False, False, False], ... 'b': [False, True, False, True, False], ... 'c': [True, True, True, True, True] ... }, index=index) >>> portfolio = vbt.Portfolio.from_signals( ... price, entries, exits, size=10, ... init_capital=100, fees=0.0025, fixed_fees=1., slippage=0.001) >>> portfolio.orders.records col idx size price fees side 0 0 0 10.0 1.001 1.025025 0 1 1 0 10.0 1.001 1.025025 0 2 1 1 10.0 1.998 1.049950 1 3 1 2 10.0 3.003 1.075075 0 4 1 3 10.0 1.998 1.049950 1 5 1 4 10.0 1.001 1.025025 0 6 2 0 10.0 1.001 1.025025 0 >>> portfolio.equity a b c 2020-01-01 98.964975 98.964975 98.964975 2020-01-02 108.964975 107.895025 108.964975 2020-01-03 118.964975 106.789950 118.964975 2020-01-04 108.964975 95.720000 108.964975 2020-01-05 98.964975 94.684975 98.964975 ``` """ # Get defaults if entry_price is None: entry_price = main_price if exit_price is None: exit_price = main_price if init_capital is None: init_capital = defaults.portfolio['init_capital'] if size is None: size = defaults.portfolio['size'] if size_type is None: size_type = defaults.portfolio['size_type'] if fees is None: fees = defaults.portfolio['fees'] if fixed_fees is None: fixed_fees = defaults.portfolio['fixed_fees'] if slippage is None: slippage = defaults.portfolio['slippage'] # Perform checks checks.assert_type(main_price, (pd.Series, pd.DataFrame)) checks.assert_dtype(entries, np.bool_) checks.assert_dtype(exits, np.bool_) # Broadcast inputs # Only main_price is broadcasted, others can remain unchanged thanks to flexible indexing keep_raw = (False, True, True, True, True, True, True, True, True, True, True) main_price, entries, exits, size, size_type, entry_price, \ exit_price, fees, fixed_fees, slippage, init_capital = \ reshape_fns.broadcast( main_price, entries, exits, size, size_type, entry_price, exit_price, fees, fixed_fees, slippage, init_capital, **broadcast_kwargs, writeable=True, keep_raw=keep_raw) target_shape = (main_price.shape[0], main_price.shape[1] if main_price.ndim > 1 else 1) # Perform calculation order_records, cash, shares = nb.simulate_from_signals_nb( target_shape, init_capital, entries, exits, size, size_type, entry_price, exit_price, fees, fixed_fees, slippage, accumulate, is_2d=main_price.ndim == 2) # Bring to the same meta cash = main_price.vbt.wrap(cash) shares = main_price.vbt.wrap(shares) orders = Orders(order_records, main_price, freq=freq) if checks.is_series(main_price): init_capital = init_capital.item(0) else: init_capital = np.broadcast_to(init_capital, (target_shape[1], )) init_capital = main_price.vbt.wrap_reduced(init_capital) return cls(main_price, init_capital, orders, cash, shares, freq=freq, **kwargs)
def from_orders(cls, main_price, order_size, size_type=SizeType.Shares, order_price=None, init_capital=None, fees=None, fixed_fees=None, slippage=None, broadcast_kwargs={}, freq=None, **kwargs): """Build portfolio from orders. Starting with initial capital `init_capital`, at each time step, orders the number of shares specified in `order_size` for `order_price`. For more details, see `vectorbt.portfolio.nb.simulate_from_orders_nb`. Args: main_price (pandas_like): Main price of the asset, such as close. Will broadcast. order_size (float or array_like): The amount of shares to order. Will broadcast. If the size is positive, this is the number of shares to buy. If the size is negative, this is the number of shares to sell. To buy/sell everything, set the size to `np.inf`. size_type (int or array_like): See `vectorbt.portfolio.enums.SizeType`. order_price (array_like): Order price. Defaults to `main_price`. Will broadcast. init_capital (float or array_like): The initial capital. Will broadcast. Allowed is either a single value or value per column. fees (float or array_like): Fees in percentage of the order value. Will broadcast. fixed_fees (float or array_like): Fixed amount of fees to pay per order. Will broadcast. slippage (float or array_like): Slippage in percentage of price. Will broadcast. broadcast_kwargs: Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`. freq (any): Index frequency in case `main_price.index` is not datetime-like. **kwargs: Keyword arguments passed to the `__init__` method. For defaults, see `vectorbt.defaults.portfolio`. All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`. At the end, they will have the same metadata. Example: Portfolio from various order sequences: ```python-repl >>> portfolio = vbt.Portfolio.from_orders(price, orders, ... init_capital=100, fees=0.0025, fixed_fees=1., slippage=0.001) >>> portfolio.orders.records col idx size price fees side 0 0 0 98.654463 1.001 1.246883 0 1 1 0 1.000000 1.001 1.002502 0 2 1 1 1.000000 2.002 1.005005 0 3 1 2 1.000000 3.003 1.007507 0 4 1 3 1.000000 2.002 1.005005 0 5 1 4 4.000000 0.999 1.009990 1 6 2 0 98.654463 1.001 1.246883 0 7 2 1 98.654463 1.998 1.492779 1 8 2 2 64.646521 3.003 1.485334 0 9 2 3 64.646521 1.998 1.322909 1 10 2 4 126.398131 1.001 1.316311 0 >>> portfolio.equity a b c 2020-01-01 98.654463 98.996498 98.654463 2020-01-02 197.308925 98.989493 195.618838 2020-01-03 295.963388 99.978985 193.939564 2020-01-04 197.308925 95.971980 127.840840 2020-01-05 98.654463 90.957990 126.398131 ``` """ # Get defaults if order_price is None: order_price = main_price if init_capital is None: init_capital = defaults.portfolio['init_capital'] if fees is None: fees = defaults.portfolio['fees'] if fixed_fees is None: fixed_fees = defaults.portfolio['fixed_fees'] if slippage is None: slippage = defaults.portfolio['slippage'] # Perform checks checks.assert_type(main_price, (pd.Series, pd.DataFrame)) # Broadcast inputs # Only main_price is broadcasted, others can remain unchanged thanks to flexible indexing keep_raw = (False, True, True, True, True, True, True, True) main_price, order_size, size_type, order_price, fees, fixed_fees, slippage, init_capital = \ reshape_fns.broadcast( main_price, order_size, size_type, order_price, fees, fixed_fees, slippage, init_capital, **broadcast_kwargs, writeable=True, keep_raw=keep_raw) target_shape = (main_price.shape[0], main_price.shape[1] if main_price.ndim > 1 else 1) # Perform calculation order_records, cash, shares = nb.simulate_from_orders_nb( target_shape, init_capital, order_size, size_type, order_price, fees, fixed_fees, slippage, is_2d=main_price.ndim == 2) # Bring to the same meta cash = main_price.vbt.wrap(cash) shares = main_price.vbt.wrap(shares) orders = Orders(order_records, main_price, freq=freq) if checks.is_series(main_price): init_capital = init_capital.item(0) else: init_capital = np.broadcast_to(init_capital, (target_shape[1], )) init_capital = main_price.vbt.wrap_reduced(init_capital) return cls(main_price, init_capital, orders, cash, shares, freq=freq, **kwargs)
def from_order_func(cls, main_price, order_func_nb, *args, init_capital=None, freq=None, **kwargs): """Build portfolio from a custom order function. Starting with initial capital `init_capital`, iterates over shape `main_price.shape`, and for each data point, generates an order using `order_func_nb`. This way, you can specify order size, price and transaction costs dynamically (for example, based on the current balance). To iterate over a bigger shape than `main_price`, you should tile/repeat `main_price` to the desired shape. Args: main_price (pandas_like): Main price of the asset, such as close. Must be a pandas object. order_func_nb (function): Function that returns an order. See `vectorbt.portfolio.enums.Order`. *args: Arguments passed to `order_func_nb`. init_capital (int, float or array_like): The initial capital. Single value or value per column. freq (any): Index frequency in case `main_price.index` is not datetime-like. **kwargs: Keyword arguments passed to the `__init__` method. For defaults, see `vectorbt.defaults.portfolio`. All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`. At the end, they will have the same metadata. !!! note `order_func_nb` must be Numba-compiled. Example: Portfolio from buying daily: ```python-repl >>> from vectorbt.portfolio import Order >>> @njit ... def order_func_nb(col, i, run_cash, run_shares, price): ... return Order(10, price[i], fees=0.01, fixed_fees=1., slippage=0.01) >>> portfolio = vbt.Portfolio.from_order_func( ... price, order_func_nb, price.values, init_capital=100) >>> print(portfolio.orders.records) col idx size price fees side 0 0 0 10.0 1.01 1.101 0 1 0 1 10.0 2.02 1.202 0 2 0 2 10.0 3.03 1.303 0 3 0 3 10.0 2.02 1.202 0 4 0 4 10.0 1.01 1.101 0 >>> print(portfolio.equity) 2020-01-01 98.799 2020-01-02 107.397 2020-01-03 125.794 2020-01-04 94.392 2020-01-05 53.191 Name: a, dtype: float64 ``` """ # Get defaults if init_capital is None: init_capital = defaults.portfolio['init_capital'] # Perform checks checks.assert_type(main_price, (pd.Series, pd.DataFrame)) checks.assert_numba_func(order_func_nb) # Broadcast inputs target_shape = (main_price.shape[0], main_price.shape[1] if main_price.ndim > 1 else 1) init_capital = np.broadcast_to(init_capital, (target_shape[1],)) # Perform calculation order_records, cash, shares = nb.simulate_nb( target_shape, init_capital, order_func_nb, *args) # Bring to the same meta wrapper = ArrayWrapper.from_obj(main_price, freq=freq) cash = wrapper.wrap(cash) shares = wrapper.wrap(shares) orders = Orders(order_records, main_price, freq=freq) if checks.is_series(main_price): init_capital = init_capital[0] else: init_capital = wrapper.wrap_reduced(init_capital) return cls(main_price, init_capital, orders, cash, shares, freq=freq, **kwargs)