def from_signals(cls, ts, entries, exits, volume=np.inf, accumulate=False, investment=None, slippage=None, commission=None, broadcast_kwargs={}): """Build portfolio based on entry and exit signals and the corresponding volume. Set volume to the number of shares to buy/sell. Set volume to np.inf to buy/sell everything. Set accumulate to False to avoid producing new orders if already in the market.""" if investment is None: investment = defaults.portfolio['investment'] if slippage is None: slippage = defaults.portfolio['slippage'] if commission is None: commission = defaults.portfolio['commission'] checks.assert_type(ts, (pd.Series, pd.DataFrame)) checks.assert_type(entries, (pd.Series, pd.DataFrame)) checks.assert_type(exits, (pd.Series, pd.DataFrame)) ts.vbt.timeseries.validate() entries.vbt.signals.validate() exits.vbt.signals.validate() ts, entries, exits = reshape_fns.broadcast(ts, entries, exits, **broadcast_kwargs, writeable=True) volume = reshape_fns.broadcast_to(volume, ts, writeable=True, copy_kwargs={'dtype': np.float64}) investment = float(investment) slippage = float(slippage) commission = float(commission) cash, shares = nb.portfolio_from_signals_np(ts.vbt.to_2d_array(), investment, slippage, commission, entries.vbt.to_2d_array(), exits.vbt.to_2d_array(), volume.vbt.to_2d_array(), accumulate) cash = ts.vbt.wrap_array(cash) shares = ts.vbt.wrap_array(shares) return cls(ts, cash, shares, investment, slippage, commission)
def mapper_indexing_func(mapper, ref_obj, pd_indexing_func): """Broadcast `mapper` Series to `ref_obj` and perform pandas indexing using `pd_indexing_func`.""" checks.assert_type(mapper, pd.Series) checks.assert_type(ref_obj, (pd.Series, pd.DataFrame)) df_range_mapper = reshape_fns.broadcast_to(np.arange(len(mapper.index)), ref_obj) loced_range_mapper = pd_indexing_func(df_range_mapper) new_mapper = mapper.iloc[loced_range_mapper.values[0]] if checks.is_frame(loced_range_mapper): return pd.Series(new_mapper.values, index=loced_range_mapper.columns, name=mapper.name) elif checks.is_series(loced_range_mapper): return pd.Series([new_mapper], index=[loced_range_mapper.name], name=mapper.name)
def __init__(self, price, init_capital, order_records, cash, shares, data_freq=None, year_freq=None, risk_free=None, required_return=None, cutoff=None, factor_returns=None): # Perform checks checks.assert_type(price, (pd.Series, pd.DataFrame)) checks.assert_type(order_records, np.ndarray) checks.assert_same_shape(order_records, OrderRecord, axis=(1, 0)) checks.assert_same_meta(price, cash) checks.assert_same_meta(price, shares) # Main parameters self._price = price self._init_capital = init_capital self._order_records = order_records self._cash = cash self._shares = shares # Other parameters if data_freq is None: data_freq = price.vbt.timeseries.timedelta else: data_freq = pd.to_timedelta(data_freq) self._data_freq = data_freq year_freq = defaults.portfolio[ 'year_freq'] if year_freq is None else year_freq year_freq = pd.to_timedelta(year_freq) self._year_freq = year_freq self._ann_factor = year_freq / data_freq self._risk_free = defaults.portfolio[ 'risk_free'] if risk_free is None else risk_free self._required_return = defaults.portfolio[ 'required_return'] if required_return is None else required_return self._cutoff = defaults.portfolio[ 'cutoff'] if cutoff is None else cutoff if factor_returns is not None: factor_returns = reshape_fns.broadcast_to(factor_returns, price) self._factor_returns = factor_returns # Supercharge self.wrapper = TSRArrayWrapper.from_obj(price)
def __init__(self, price, cash, shares, init_capital, fees_paid, slippage_paid, data_freq=None, year_freq=None, risk_free=None, required_return=None, cutoff=None, factor_returns=None): checks.assert_type(price, (pd.Series, pd.DataFrame)) checks.assert_same_meta(price, cash) checks.assert_same_meta(price, shares) checks.assert_same_meta(price, fees_paid) checks.assert_same_meta(price, slippage_paid) # Time series self._price = price self._cash = cash self._shares = shares self._fees_paid = fees_paid self._slippage_paid = slippage_paid # User-defined parameters self._init_capital = init_capital if data_freq is None: data_freq = price.vbt.timeseries.timedelta else: data_freq = pd.to_timedelta(data_freq) self._data_freq = data_freq year_freq = defaults.portfolio[ 'year_freq'] if year_freq is None else year_freq year_freq = pd.to_timedelta(year_freq) self._year_freq = year_freq self._ann_factor = year_freq / data_freq self._risk_free = defaults.portfolio[ 'risk_free'] if risk_free is None else risk_free self._required_return = defaults.portfolio[ 'required_return'] if required_return is None else required_return self._cutoff = defaults.portfolio[ 'cutoff'] if cutoff is None else cutoff if factor_returns is not None: factor_returns = reshape_fns.broadcast_to(factor_returns, price) self._factor_returns = factor_returns ArrayWrapper.__init__(self, self.price)
def from_order_func(cls, price, order_func_nb, *args, init_capital=None, fees=None, slippage=None): """Build portfolio from a custom order function. Starting with initial capital `init_capital`, at each time step, orders the number of shares returned by `order_func_nb`. Args: price (pandas_like): Price of the asset. order_func_nb (function): Function that returns the amount of shares to order. See `vectorbt.portfolio.nb.portfolio_nb`. *args: Arguments passed to `order_func_nb`. init_capital (int or float): The initial capital. fees (float or array_like): Trading fees in percentage of the value involved. slippage (float or array_like): Slippage in percentage of `price`. All array-like arguments will be broadcasted together using `vectorbt.utils.reshape_fns.broadcast` with `broadcast_kwargs`. At the end, each time series object will have the same metadata. !!! note `order_func_nb` must be Numba-compiled. Example: Portfolio value of a simple buy-and-hold strategy: ```python-repl >>> @njit ... def order_func_nb(col, i, run_cash, run_shares): ... return 10 if i == 0 else 0 >>> portfolio = vbt.Portfolio.from_order_func(price, ... order_func_nb, init_capital=100, fees=0.0025) >>> print(portfolio.cash) 2018-01-01 89.975 2018-01-02 89.975 2018-01-03 89.975 2018-01-04 89.975 2018-01-05 89.975 dtype: float64 >>> print(portfolio.shares) 2018-01-01 10.0 2018-01-02 10.0 2018-01-03 10.0 2018-01-04 10.0 2018-01-05 10.0 dtype: float64 >>> print(portfolio.equity) 2018-01-01 99.975 2018-01-02 109.975 2018-01-03 119.975 2018-01-04 109.975 2018-01-05 99.975 dtype: float64 >>> print(portfolio.total_costs) 0.02499999999999858 ``` """ # Get defaults if init_capital is None: init_capital = defaults.portfolio['init_capital'] init_capital = float(init_capital) if fees is None: fees = defaults.portfolio['fees'] if slippage is None: slippage = defaults.portfolio['slippage'] # Perform checks checks.assert_type(price, (pd.Series, pd.DataFrame)) checks.assert_numba_func(order_func_nb) # Broadcast inputs fees = reshape_fns.broadcast_to(fees, price, to_pd=False, writeable=True) slippage = reshape_fns.broadcast_to(slippage, price, to_pd=False, writeable=True) # Perform calculation cash, shares, paid_fees, paid_slippage = nb.portfolio_nb( reshape_fns.to_2d(price, raw=True), init_capital, reshape_fns.to_2d(fees, raw=True), reshape_fns.to_2d(slippage, raw=True), order_func_nb, *args) # Bring to the same meta cash = price.vbt.wrap_array(cash) shares = price.vbt.wrap_array(shares) paid_fees = price.vbt.wrap_array(paid_fees) paid_slippage = price.vbt.wrap_array(paid_slippage) return cls(price, cash, shares, init_capital, paid_fees, paid_slippage)
def from_orders(cls, price, orders, is_target=False, init_capital=None, fees=None, slippage=None, broadcast_kwargs={}): """Build portfolio from orders. Starting with initial capital `init_capital`, at each time step, orders the number of shares specified in `orders`. Args: price (pandas_like): Price of the asset. orders (int, float or array_like): The amount of shares to order. If the amount is positive, this is the number of shares to buy. If the amount is negative, this is the number of shares to sell. To buy/sell everything, set the amount to `numpy.inf`. is_target (bool): If `True`, will order the difference between current and target amount. init_capital (int or float): The initial capital. fees (float or array_like): Trading fees in percentage of the value involved. slippage (float or array_like): Slippage in percentage of `price`. All array-like arguments will be broadcasted together using `vectorbt.utils.reshape_fns.broadcast` with `broadcast_kwargs`. At the end, each time series object will have the same metadata. Example: Portfolio value of various order sequences: ```python-repl >>> orders = pd.DataFrame({ ... 'a': [np.inf, 0, 0, 0, 0], ... 'b': [1, 1, 1, 1, -np.inf], ... 'c': [np.inf, -np.inf, np.inf, -np.inf, np.inf] ... }, index=index) >>> portfolio = vbt.Portfolio.from_orders(price, orders, ... init_capital=100, fees=0.0025) >>> print(portfolio.cash) a b c 2018-01-01 0.0 98.9975 0.000000 2018-01-02 0.0 96.9925 199.002494 2018-01-03 0.0 93.9850 0.000000 2018-01-04 0.0 91.9800 132.006642 2018-01-05 0.0 95.9700 0.000000 >>> print(portfolio.shares) a b c 2018-01-01 99.750623 1.0 99.750623 2018-01-02 99.750623 2.0 0.000000 2018-01-03 99.750623 3.0 66.168743 2018-01-04 99.750623 4.0 0.000000 2018-01-05 99.750623 0.0 131.677448 >>> print(portfolio.equity) a b c 2018-01-01 99.750623 99.9975 99.750623 2018-01-02 199.501247 100.9925 199.002494 2018-01-03 299.251870 102.9850 198.506228 2018-01-04 199.501247 99.9800 132.006642 2018-01-05 99.750623 95.9700 131.677448 >>> print(portfolio.total_costs) a 0.249377 b 0.030000 c 1.904433 dtype: float64 ``` """ # Get defaults if init_capital is None: init_capital = defaults.portfolio['init_capital'] init_capital = float(init_capital) if fees is None: fees = defaults.portfolio['fees'] if slippage is None: slippage = defaults.portfolio['slippage'] # Perform checks checks.assert_type(price, (pd.Series, pd.DataFrame)) checks.assert_type(orders, (pd.Series, pd.DataFrame)) # Broadcast inputs price, orders = reshape_fns.broadcast(price, orders, **broadcast_kwargs, writeable=True) fees = reshape_fns.broadcast_to(fees, price, to_pd=False, writeable=True) slippage = reshape_fns.broadcast_to(slippage, price, to_pd=False, writeable=True) # Perform calculation cash, shares, paid_fees, paid_slippage = nb.portfolio_nb( reshape_fns.to_2d(price, raw=True), init_capital, reshape_fns.to_2d(fees, raw=True), reshape_fns.to_2d(slippage, raw=True), nb.amount_order_func_nb, reshape_fns.to_2d(orders, raw=True), is_target) # Bring to the same meta cash = price.vbt.wrap_array(cash) shares = price.vbt.wrap_array(shares) paid_fees = price.vbt.wrap_array(paid_fees) paid_slippage = price.vbt.wrap_array(paid_slippage) return cls(price, cash, shares, init_capital, paid_fees, paid_slippage)
def plot_against(self, other, name=None, other_name=None, above_trace_kwargs={}, below_trace_kwargs={}, other_trace_kwargs={}, equal_trace_kwargs={}, fig=None, **layout_kwargs): """Plot Series against `other` as markers. Args: other (float, int, or array_like): The other time series/value. name (str): Name of the time series. other_name (str): Name of the other time series/value. other_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `other`. above_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for values above `other`. below_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for values below `other`. equal_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for values equal `other`. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. Example: Can plot against single values such as benchmarks. ```py df['a'].vbt.timeseries.plot_against(3) ``` ![](/vectorbt/docs/img/timeseries_plot_against_line.png) But also against other time series. ```py df['a'].vbt.timeseries.plot_against(df['b']) ``` ![](/vectorbt/docs/img/timeseries_plot_against_series.png)""" if name is None: name = self._obj.name if other_name is None: other_name = getattr(other, 'name', None) # Prepare data other = reshape_fns.to_1d(other) other = reshape_fns.broadcast_to(other, self._obj) above_obj = self._obj[self._obj > other] below_obj = self._obj[self._obj < other] equal_obj = self._obj[self._obj == other] # Set up figure if fig is None: fig = DefaultFigureWidget() fig.update_layout(**layout_kwargs) # Plot other other_scatter = go.Scatter(x=other.index, y=other, line=dict( color="grey", width=2, dash="dot", ), name=other_name, showlegend=other_name is not None) other_scatter.update(**other_trace_kwargs) fig.add_trace(other_scatter) # Plot markets above_scatter = go.Scatter(x=above_obj.index, y=above_obj, mode='markers', marker=dict(symbol='circle', color='green', size=10), name=f'{name} (above)', showlegend=name is not None) above_scatter.update(**above_trace_kwargs) fig.add_trace(above_scatter) below_scatter = go.Scatter(x=below_obj.index, y=below_obj, mode='markers', marker=dict(symbol='circle', color='red', size=10), name=f'{name} (below)', showlegend=name is not None) below_scatter.update(**below_trace_kwargs) fig.add_trace(below_scatter) equal_scatter = go.Scatter(x=equal_obj.index, y=equal_obj, mode='markers', marker=dict(symbol='circle', color='grey', size=10), name=f'{name} (equal)', showlegend=name is not None) equal_scatter.update(**equal_trace_kwargs) fig.add_trace(equal_scatter) # If other is a straight line, make y-axis symmetric if np.all(other.values == other.values.item(0)): maxval = np.nanmax(np.abs(self.to_array())) space = 0.1 * 2 * maxval y = other.values.item(0) fig.update_layout( yaxis=dict(range=[y - (maxval + space), y + maxval + space]), shapes=[ dict(type="line", xref="paper", yref='y', x0=0, x1=1, y0=y, y1=y, line=dict( color="grey", width=2, dash="dot", )) ]) return fig
def broadcast_to(self, other, **kwargs): """See `vectorbt.utils.reshape_fns.broadcast_to`.""" if isinstance(other, Base_Accessor): other = other._obj return reshape_fns.broadcast_to(self._obj, other, **kwargs)
def broadcast_to(self, other, **kwargs): if isinstance(other, Base_Accessor): other = other._obj return reshape_fns.broadcast_to(self._obj, other, **kwargs)