# ################################## HOW TO USE #################################### # # # # This is a Jupyter notebook formatted as a script # # Format: https://jupytext.readthedocs.io/en/latest/formats.html#the-percent-format # # # # Save this file and remove the '.txt' extension # # In Jupyter Lab, right click on the Python file -> Open With -> Jupytext Notebook # # Make sure to have Jupytext installed: https://github.com/mwouts/jupytext # # # # ################################################################################## # # %% [markdown] # # Portfolio # ## Asset weighting { .mdx-pulse title="Recently added" } # %% data = vbt.YFData.pull(["AAPL", "MSFT", "GOOG"], start="2020") pf = data.run("from_random_signals", n=vbt.Default(50), seed=42, group_by=True) pf.get_sharpe_ratio(group_by=False) # %% pf.sharpe_ratio # %% prices = pf.get_value(group_by=False) weights = vbt.pypfopt_optimize(prices=prices) weights # %% weighted_pf = pf.apply_weights(weights, rescale=True) weighted_pf.weights # %% weighted_pf.get_sharpe_ratio(group_by=True) # %% [markdown] # ## Position views { .mdx-pulse title="Recently added" } # %% data = vbt.YFData.pull("BTC-USD") fast_sma = data.run("talib_func:sma", timeperiod=20) slow_sma = data.run("talib_func:sma", timeperiod=50) long_entries = fast_sma.vbt.crossed_above(slow_sma) short_entries = fast_sma.vbt.crossed_below(slow_sma) pf = vbt.PF.from_signals( data, long_entries=long_entries, short_entries=short_entries, fees=0.01, fixed_fees=1.0 ) long_pf = pf.long_view short_pf = pf.short_view fig = vbt.make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.01) fig = long_pf.assets.vbt.plot_against( 0, trace_kwargs=dict(name="Long position", line_shape="hv", line_color="mediumseagreen"), other_trace_kwargs=dict(visible=False), add_trace_kwargs=dict(row=1, col=1), fig=fig ) fig = short_pf.assets.vbt.plot_against( 0, trace_kwargs=dict(name="Short position", line_shape="hv", line_color="coral"), other_trace_kwargs=dict(visible=False), add_trace_kwargs=dict(row=2, col=1), fig=fig ) fig.show() # %% long_pf.sharpe_ratio # %% short_pf.sharpe_ratio # %% [markdown] # ## Index records # %% data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], missing_index="drop") records = [ dict(date="2022", symbol="BTC-USD", long_entry=True), dict(date="2022", symbol="ETH-USD", short_entry=True), dict(row=-1, exit=True), ] pf = vbt.PF.from_signals(data, records=records) pf.orders.readable # %% [markdown] # ## Portfolio preparers # %% data = vbt.YFData.pull("BTC-USD", end="2017-01") prep_result = vbt.PF.from_holding( data, stop_ladder="uniform", tp_stop=vbt.Param([ [0.1, 0.2, 0.3, 0.4, 0.5], [0.4, 0.5, 0.6], ], keys=["tp_ladder_1", "tp_ladder_2"]), return_prep_result=True ) prep_result.target_args["tp_stop"] # %% new_tp_stop = prep_result.target_args["tp_stop"] + 0.1 new_prep_result = prep_result.replace(target_args=dict(tp_stop=new_tp_stop), nested_=True) new_prep_result.target_args["tp_stop"] # %% pf = vbt.PF.from_signals(new_prep_result) pf.total_return # %% sim_out = new_prep_result.target_func(**new_prep_result.target_args) pf = vbt.PF(order_records=sim_out, **new_prep_result.pf_args) pf.total_return # %% [markdown] # ## Stop laddering # %% data = vbt.YFData.pull("BTC-USD", end="2017-01") pf = vbt.PF.from_holding( data, stop_ladder="uniform", tp_stop=vbt.Param([ [0.1, 0.2, 0.3, 0.4, 0.5], [0.4, 0.5, 0.6], ], keys=["tp_ladder_1", "tp_ladder_2"]) ) pf.trades.plot(column="tp_ladder_1").show() # %% [markdown] # ## Staticization # %% data = vbt.YFData.pull("BTC-USD") pf = vbt.PF.from_signals( data, signal_func_nb="signal_func_nb.py", signal_args=(vbt.Rep("fast_sma"), vbt.Rep("slow_sma")), broadcast_named_args=dict( fast_sma=data.run("sma", 20, hide_params=True, unpack=True), slow_sma=data.run("sma", 50, hide_params=True, unpack=True) ), staticized=True ) # %% [markdown] # ## Position info # %% @njit def signal_func_nb(c, entries, exits): is_entry = vbt.pf_nb.select_nb(c, entries) is_exit = vbt.pf_nb.select_nb(c, exits) if is_entry: return True, False, False, False if is_exit: pos_info = c.last_pos_info[c.col] if pos_info["status"] == vbt.pf_enums.TradeStatus.Open: if pos_info["pnl"] >= 0: return False, True, False, False return False, False, False, False data = vbt.YFData.pull("BTC-USD") entries, exits = data.run("RANDNX", n=10, unpack=True) pf = vbt.Portfolio.from_signals( data, signal_func_nb=signal_func_nb, signal_args=(vbt.Rep("entries"), vbt.Rep("exits")), broadcast_named_args=dict(entries=entries, exits=exits), jitted=False ) pf.trades.readable[["Entry Index", "Exit Index", "PnL"]] # %% [markdown] # ## Time stops # %% data = vbt.YFData.pull("BTC-USD", start="2022-01", end="2022-04") entries = vbt.pd_acc.signals.generate_random(data.symbol_wrapper, n=10) pf = vbt.PF.from_signals(data, entries, dt_stop="M") pf.orders.readable[["Fill Index", "Side", "Stop Type"]] # %% [markdown] # ## Target size to signals # %% data = vbt.YFData.pull( ["SPY", "TLT", "XLF", "XLE", "XLU", "XLK", "XLB", "XLP", "XLY", "XLI", "XLV"], start="2022", end="2023", missing_index="drop" ) pfo = vbt.PFO.from_riskfolio(data.returns, every="M") pf = pfo.simulate( data, pf_method="from_signals", sl_stop=0.05, tp_stop=0.1, stop_exit_price="close" ) pf.plot_allocations().show() # %% [markdown] # ## Target price # %% data = vbt.YFData.pull("BTC-USD") pf = vbt.PF.from_random_signals( data, n=100, sl_stop=data.low.vbt.ago(1), delta_format="target" ) sl_orders = pf.orders.stop_type_sl signal_index = pf.wrapper.index[sl_orders.signal_idx.values] hit_index = pf.wrapper.index[sl_orders.idx.values] hit_after = hit_index - signal_index hit_after # %% [markdown] # ## Leverage # %% data = vbt.YFData.pull("BTC-USD", start="2020", end="2022") pf = vbt.PF.from_random_signals( data, n=100, leverage=vbt.Param([0.5, 1, 2, 3]), ) pf.value.vbt.plot().show() # %% [markdown] # ## Order delays # %% pf = vbt.PF.from_random_signals( vbt.YFData.pull("BTC-USD", start="2021-01", end="2021-02"), n=3, price=vbt.Param(["close", "nextopen"]) ) fig = pf.orders["close"].plot( buy_trace_kwargs=dict(name="Buy (close)", marker=dict(symbol="triangle-up-open")), sell_trace_kwargs=dict(name="Buy (close)", marker=dict(symbol="triangle-down-open")) ) pf.orders["nextopen"].plot( plot_ohlc=False, plot_close=False, buy_trace_kwargs=dict(name="Buy (nextopen)"), sell_trace_kwargs=dict(name="Sell (nextopen)"), fig=fig ) fig.show() # %% [markdown] # ## Signal callbacks # %% InOutputs = namedtuple("InOutputs", ["fast_sma", "slow_sma"]) def initialize_in_outputs(target_shape): return InOutputs( fast_sma=np.full(target_shape, np.nan), slow_sma=np.full(target_shape, np.nan) ) @njit def signal_func_nb(c, fast_window, slow_window): fast_sma = c.in_outputs.fast_sma slow_sma = c.in_outputs.slow_sma fast_start_i = c.i - fast_window + 1 slow_start_i = c.i - slow_window + 1 if fast_start_i >= 0 and slow_start_i >= 0: fast_sma[c.i, c.col] = np.nanmean(c.close[fast_start_i : c.i + 1]) slow_sma[c.i, c.col] = np.nanmean(c.close[slow_start_i : c.i + 1]) is_entry = vbt.pf_nb.iter_crossed_above_nb(c, fast_sma, slow_sma) is_exit = vbt.pf_nb.iter_crossed_below_nb(c, fast_sma, slow_sma) return is_entry, is_exit, False, False return False, False, False, False pf = vbt.PF.from_signals( vbt.YFData.pull("BTC-USD"), signal_func_nb=signal_func_nb, signal_args=(50, 200), in_outputs=vbt.RepFunc(initialize_in_outputs), ) fig = pf.get_in_output("fast_sma").vbt.plot() pf.get_in_output("slow_sma").vbt.plot(fig=fig) pf.orders.plot(plot_ohlc=False, plot_close=False, fig=fig) fig.show() # %% [markdown] # ## Limit orders # %% pf = vbt.PF.from_random_signals( vbt.YFData.pull("BTC-USD"), n=100, order_type="limit", limit_delta=vbt.Param(np.arange(0.001, 0.1, 0.001)), ) pf.orders.count().vbt.plot( xaxis_title="Limit delta", yaxis_title="Order count" ).show() # %% [markdown] # ## Delta formats # %% data = vbt.YFData.pull("BTC-USD") atr = vbt.talib("ATR").run(data.high, data.low, data.close).real pf = vbt.PF.from_holding( data.loc["2022-01-01":"2022-01-07"], sl_stop=atr.loc["2022-01-01":"2022-01-07"], delta_format="absolute" ) pf.orders.plot().show() # %% [markdown] # ## Bar skipping # %% data = vbt.BinanceData.pull("BTCUSDT", start="one month ago UTC", timeframe="minute") size = data.symbol_wrapper.fill(np.nan) size[0] = np.inf # %% %%timeit vbt.PF.from_orders(data, size, ffill_val_price=True) # %% %%timeit vbt.PF.from_orders(data, size, ffill_val_price=False) # %% [markdown] # ## Signal contexts # %% @njit def entry_place_func_nb(c, index): for i in range(c.from_i, c.to_i): if i == 0: return i - c.from_i else: index_before = index[i - 1] index_now = index[i] index_next_week = vbt.dt_nb.future_weekday_nb(index_before, 0) if index_now >= index_next_week: return i - c.from_i return -1 @njit def exit_place_func_nb(c, index): for i in range(c.from_i, c.to_i): if i == len(index) - 1: return i - c.from_i else: index_now = index[i] index_after = index[i + 1] index_next_week = vbt.dt_nb.future_weekday_nb(index_now, 0) if index_after >= index_next_week: return i - c.from_i return -1 data = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2020-01-14") entries, exits = vbt.pd_acc.signals.generate_both( data.symbol_wrapper.shape, entry_place_func_nb=entry_place_func_nb, entry_place_args=(data.index.vbt.to_ns(),), exit_place_func_nb=exit_place_func_nb, exit_place_args=(data.index.vbt.to_ns(),), wrapper=data.symbol_wrapper ) pd.concat(( entries.rename("Entries"), exits.rename("Exits") ), axis=1).to_period("W") # %% [markdown] # ## Pre-computation # %% data = vbt.YFData.pull("BTC-USD") # %% %%timeit for n in range(1000): pf = vbt.PF.from_random_signals(data, n=n, save_returns=False) pf.sharpe_ratio # %% %%timeit pf = vbt.PF.from_random_signals(data, n=np.arange(1000).tolist(), save_returns=False) pf.sharpe_ratio # %% %%timeit pf = vbt.PF.from_random_signals(data, n=np.arange(1000).tolist(), save_returns=True) pf.sharpe_ratio # %% [markdown] # ## Cash deposits # %% data = vbt.YFData.pull("BTC-USD") cash_deposits = data.symbol_wrapper.fill(0.0) month_start_mask = ~data.index.tz_convert(None).to_period("M").duplicated() cash_deposits[month_start_mask] = 10 pf = vbt.PF.from_orders( data.close, init_cash=0, cash_deposits=cash_deposits ) pf.input_value # %% pf.final_value # %% [markdown] # ## Cash earnings # %% data = vbt.YFData.pull("AAPL", start="2010") pf_kept = vbt.PF.from_holding( data.close, cash_dividends=data.get("Dividends") ) pf_kept.cash.iloc[-1] # %% pf_kept.assets.iloc[-1] # %% pf_reinvested = vbt.PF.from_orders( data.close, cash_dividends=data.get("Dividends") ) pf_reinvested.cash.iloc[-1] # %% pf_reinvested.assets.iloc[-1] # %% fig = pf_kept.value.rename("Value (kept)").vbt.plot() pf_reinvested.value.rename("Value (reinvested)").vbt.plot(fig=fig) fig.show() # %% [markdown] # ## In-outputs # %% data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], missing_index="drop") size = data.symbol_wrapper.fill(np.nan) rand_indices = np.random.choice(np.arange(len(size)), 10) size.iloc[rand_indices[0::2]] = -np.inf size.iloc[rand_indices[1::2]] = np.inf @njit def post_segment_func_nb(c): for col in range(c.from_col, c.to_col): col_debt = c.last_debt[col] c.in_outputs.debt[c.i, col] = col_debt if col_debt > c.in_outputs.max_debt[col]: c.in_outputs.max_debt[col] = col_debt pf = vbt.PF.from_def_order_func( data.close, size=size, post_segment_func_nb=post_segment_func_nb, in_outputs=dict( debt=vbt.RepEval("np.empty_like(close)"), max_debt=vbt.RepEval("np.full(close.shape[1], 0.)") ) ) pf.get_in_output("debt") # %% pf.get_in_output("max_debt") # %% [markdown] # ## Flexible attributes # %% data = vbt.YFData.pull("BTC-USD") pf = vbt.PF.from_random_signals(data.close, n=100) value = pf.get_value() long_exposure = vbt.PF.get_gross_exposure( asset_value=pf.get_asset_value(direction="longonly"), value=value, wrapper=pf.wrapper ) short_exposure = vbt.PF.get_gross_exposure( asset_value=pf.get_asset_value(direction="shortonly"), value=value, wrapper=pf.wrapper ) del value net_exposure = vbt.PF.get_net_exposure( long_exposure=long_exposure, short_exposure=short_exposure, wrapper=pf.wrapper ) del long_exposure del short_exposure net_exposure # %% [markdown] # ## Shortcut properties # %% data = vbt.YFData.pull("BTC-USD") size = data.symbol_wrapper.fill(np.nan) rand_indices = np.random.choice(np.arange(len(size)), 10) size.iloc[rand_indices[0::2]] = -np.inf size.iloc[rand_indices[1::2]] = np.inf @njit def post_segment_func_nb(c): for col in range(c.from_col, c.to_col): return_now = c.last_return[col] return_now = 0.5 * return_now if return_now > 0 else return_now c.in_outputs.returns[c.i, col] = return_now pf = vbt.PF.from_def_order_func( data.close, size=size, size_type="targetpercent", post_segment_func_nb=post_segment_func_nb, in_outputs=dict( returns=vbt.RepEval("np.empty_like(close)") ) ) pf.returns # %% pf.get_returns() # %%