Files
strategy-lab/to_explore/notebooks/PortfolioOptimization.ipynb

3179 lines
85 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "9b76874f-1446-4198-8695-386bbb22060f",
"metadata": {},
"outputs": [],
"source": [
"from vectorbtpro import *\n",
"# whats_imported()\n",
"\n",
"vbt.settings.set_theme(\"dark\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "db16b4c1-1ffb-41fe-a04b-249161b80b3f",
"metadata": {},
"outputs": [],
"source": [
"# data = vbt.BinanceData.pull(\n",
"# [\"BTCUSDT\", \"ETHUSDT\", \"BNBUSDT\", \"XRPUSDT\", \"ADAUSDT\"], \n",
"# start=\"2020-01-01 UTC\", \n",
"# end=\"2021-01-01 UTC\",\n",
"# timeframe=\"1h\"\n",
"# )\n",
"\n",
"# data.to_hdf()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cd78fe86-080e-49ab-bfd3-87634465cc86",
"metadata": {},
"outputs": [],
"source": [
"data = vbt.HDFData.pull(\"BinanceData.h5\")"
]
},
{
"cell_type": "markdown",
"id": "69590f7f-bde5-428c-a036-52d6bd207a72",
"metadata": {},
"source": [
"## Allocation"
]
},
{
"cell_type": "markdown",
"id": "4bec503b-c470-4255-aaf9-22cb67037586",
"metadata": {},
"source": [
"### Manually"
]
},
{
"cell_type": "markdown",
"id": "1ca8b280-a993-4501-b00e-01dbc86ceb21",
"metadata": {},
"source": [
"#### Index points"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc61be80-bc27-42be-80bf-8335285eef96",
"metadata": {},
"outputs": [],
"source": [
"ms_points = data.wrapper.get_index_points(every=\"M\")\n",
"ms_points"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ae7a4d6-ea1f-45ef-9653-3e0def80e242",
"metadata": {},
"outputs": [],
"source": [
"data.wrapper.index.get_indexer(\n",
" pd.Series(index=data.wrapper.index).resample(vbt.offset(\"M\")).asfreq().index, \n",
" method=\"bfill\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7a1475a-7cf1-42b9-aa29-454591557ce3",
"metadata": {},
"outputs": [],
"source": [
"data.wrapper.index[ms_points]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc620982-9a4b-4176-9d13-6088acac0ce6",
"metadata": {},
"outputs": [],
"source": [
"example_points = data.wrapper.get_index_points(every=24 * 30)\n",
"data.wrapper.index[example_points]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a43d5382-fe93-444d-ab7a-04736ab4b8e1",
"metadata": {},
"outputs": [],
"source": [
"date_offset = pd.offsets.WeekOfMonth(week=3, weekday=4)\n",
"example_points = data.wrapper.get_index_points(\n",
" every=date_offset, \n",
" add_delta=pd.Timedelta(hours=17)\n",
")\n",
"data.wrapper.index[example_points]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c6f534de-68f6-4e3c-b55c-2d80ac197d8e",
"metadata": {},
"outputs": [],
"source": [
"example_points = data.wrapper.get_index_points(\n",
" start=\"April 1st 2020\",\n",
" every=\"M\"\n",
")\n",
"data.wrapper.index[example_points]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85ab2969-6a78-4869-8fcb-f498cd6430b2",
"metadata": {},
"outputs": [],
"source": [
"example_points = data.wrapper.get_index_points(\n",
" on=[\"April 1st 2020 19:45\", \"17 September 2020 00:01\"]\n",
")\n",
"data.wrapper.index[example_points]"
]
},
{
"cell_type": "markdown",
"id": "5681f6ee-bb4b-473a-aaec-92af48e290ee",
"metadata": {},
"source": [
"#### Filling"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4c86c70-3611-43c7-8536-2c2b4a903d35",
"metadata": {},
"outputs": [],
"source": [
"symbol_wrapper = data.get_symbol_wrapper(freq=\"1h\")\n",
"filled_allocations = symbol_wrapper.fill()\n",
"print(filled_allocations)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e81dcdf2-0079-4adb-9b15-99486a56fc4f",
"metadata": {},
"outputs": [],
"source": [
"np.random.seed(42)\n",
"\n",
"def random_allocate_func():\n",
" weights = np.random.uniform(size=symbol_wrapper.shape[1])\n",
" return weights / weights.sum()\n",
"\n",
"for idx in ms_points:\n",
" filled_allocations.iloc[idx] = random_allocate_func()\n",
"\n",
"allocations = filled_allocations[~filled_allocations.isnull().any(axis=1)]\n",
"allocations"
]
},
{
"cell_type": "markdown",
"id": "cb975a93-4739-4f3a-b3ba-068cdf82d27e",
"metadata": {},
"source": [
"#### Simulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24345fec-cc7a-4bc1-a822-4794b5bd8995",
"metadata": {},
"outputs": [],
"source": [
"pf = vbt.Portfolio.from_orders(\n",
" close=data.get(\"Close\"),\n",
" size=filled_allocations,\n",
" size_type=\"targetpercent\",\n",
" group_by=True,\n",
" cash_sharing=True,\n",
" call_seq=\"auto\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "40e232aa-6f52-41ff-b7bb-c8f073ced4fa",
"metadata": {},
"outputs": [],
"source": [
"sim_alloc = pf.get_asset_value(group_by=False).vbt / pf.value\n",
"print(sim_alloc)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5817b120-0903-46a8-b96a-9e40b46e05be",
"metadata": {},
"outputs": [],
"source": [
"sim_alloc.vbt.plot(\n",
" trace_kwargs=dict(stackgroup=\"one\"),\n",
" use_gl=False\n",
").show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2b2fa9e-4261-4f3e-b0fc-621567101a86",
"metadata": {},
"outputs": [],
"source": [
"pf.plot_allocations().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3382d027-387f-4c6d-bd1c-e1e83ebe97e4",
"metadata": {},
"outputs": [],
"source": [
"np.isclose(allocations, sim_alloc.iloc[ms_points])"
]
},
{
"cell_type": "markdown",
"id": "6aa7e781-88cd-4fb9-b492-f249207d1d97",
"metadata": {},
"source": [
"### Allocation method"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d38deafd-ca0a-487a-8109-cf99c6eb63fe",
"metadata": {},
"outputs": [],
"source": [
"np.random.seed(42)\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" random_allocate_func,\n",
" every=\"M\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cd07c105-1448-41b7-918d-209129790757",
"metadata": {},
"outputs": [],
"source": [
"print(pfo.allocations)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1f5f65c-efd5-45e8-869b-f0f208054ec7",
"metadata": {},
"outputs": [],
"source": [
"print(pfo.filled_allocations)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9309c8f5-79a7-477b-8e19-7953a86df612",
"metadata": {},
"outputs": [],
"source": [
"print(pfo.alloc_records.records_readable)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "77ca4765-ea6e-40d5-8f51-25ded1e76168",
"metadata": {},
"outputs": [],
"source": [
"pfo.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18d794b3-f011-42e7-a7e2-300cf9f3b195",
"metadata": {},
"outputs": [],
"source": [
"pfo.stats()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "185132cb-120f-43c1-bf07-72b382e273ce",
"metadata": {},
"outputs": [],
"source": [
"pf = vbt.Portfolio.from_optimizer(data, pfo, freq=\"1h\")\n",
"\n",
"pf.sharpe_ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a398f1b4-71b8-4f67-8ce1-fd9b5cc56d56",
"metadata": {},
"outputs": [],
"source": [
"pf = pfo.simulate(data, freq=\"1h\")\n",
"\n",
"pf.sharpe_ratio"
]
},
{
"cell_type": "markdown",
"id": "82efb592-393e-4c9e-9d7a-2c50c8d3fb44",
"metadata": {},
"source": [
"#### Once"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2db279f6-4ebf-4f8c-82a2-a2cf820b740d",
"metadata": {},
"outputs": [],
"source": [
"def const_allocate_func(target_alloc):\n",
" return target_alloc\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" const_allocate_func,\n",
" [0.5, 0.2, 0.1, 0.1, 0.1]\n",
")\n",
"\n",
"pfo.plot().show_svg()"
]
},
{
"cell_type": "markdown",
"id": "a8fef0f6-976a-468a-8337-a9fc59847f96",
"metadata": {},
"source": [
"#### Parsing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5dbd26d9-8953-4f3d-9844-68c035a3d264",
"metadata": {},
"outputs": [],
"source": [
"custom_index = vbt.date_range(\"2020-01-01\", \"2021-01-01\", freq=\"Q\")\n",
"custom_allocations = pd.DataFrame(\n",
" [\n",
" [0.5, 0.2, 0.1, 0.1, 0.1],\n",
" [0.1, 0.5, 0.2, 0.1, 0.1],\n",
" [0.1, 0.1, 0.5, 0.2, 0.1],\n",
" [0.1, 0.1, 0.1, 0.5, 0.2]\n",
" ],\n",
" index=custom_index, \n",
" columns=symbol_wrapper.columns\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c2e9dc9-60c4-40ee-a725-c6d5634a7c4a",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocations(\n",
" symbol_wrapper,\n",
" custom_allocations\n",
")\n",
"print(pfo.allocations)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd3f29d5-ddab-4ba9-abd3-c09807c072f4",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocations(\n",
" symbol_wrapper,\n",
" custom_allocations.values,\n",
" start=\"2020-01-01\",\n",
" end=\"2021-01-01\",\n",
" every=\"Q\"\n",
")\n",
"print(pfo.allocations)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eeab75a5-6e4f-4751-b9e5-3fbaaf6717fe",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_filled_allocations(\n",
" pfo.fill_allocations()\n",
")\n",
"print(pfo.allocations)"
]
},
{
"cell_type": "markdown",
"id": "c254a91a-85d7-4502-a208-606a60f3d8a4",
"metadata": {},
"source": [
"#### Templates"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "128d6a58-e133-476a-a78a-2ab2eede62e7",
"metadata": {},
"outputs": [],
"source": [
"def rotation_allocate_func(wrapper, i):\n",
" weights = np.full(len(wrapper.columns), 0)\n",
" weights[i % len(wrapper.columns)] = 1\n",
" return weights\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" rotation_allocate_func,\n",
" vbt.Rep(\"wrapper\"),\n",
" vbt.Rep(\"i\"),\n",
" every=\"M\"\n",
")\n",
"\n",
"pfo.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d0fc4975-fb15-4d56-a42e-f9a3686441fe",
"metadata": {},
"outputs": [],
"source": [
"def rotation_allocate_func(symbols, chosen_symbol):\n",
" return {s: 1 if s == chosen_symbol else 0 for s in symbols}\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" rotation_allocate_func,\n",
" vbt.RepEval(\"wrapper.columns\"),\n",
" vbt.RepEval(\"wrapper.columns[i % len(wrapper.columns)]\"),\n",
" every=\"M\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0cac3042-ab4b-4f88-b361-5381b619e91e",
"metadata": {},
"outputs": [],
"source": [
"print(pfo.allocations)"
]
},
{
"cell_type": "markdown",
"id": "48324866-77d0-4743-9778-4468c6933941",
"metadata": {},
"source": [
"#### Groups"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "849f130f-aaaa-4b84-9e3d-002be887c103",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" const_allocate_func,\n",
" [0.5, 0.2, 0.1, 0.1, 0.1],\n",
" every=vbt.Param([\"1M\", \"2M\", \"3M\"])\n",
")\n",
"\n",
"pf = pfo.simulate(data, freq=\"1h\")\n",
"pf.total_return"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52154cba-8a20-43bf-934e-efcf8a07d14a",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" const_allocate_func,\n",
" vbt.Param([\n",
" [0.5, 0.2, 0.1, 0.1, 0.1],\n",
" [0.2, 0.1, 0.1, 0.1, 0.5]\n",
" ], keys=pd.Index([\"w1\", \"w2\"], name=\"weights\")),\n",
" every=vbt.Param([\"1M\", \"2M\", \"3M\"])\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fcfe8e6-a6b3-4904-9e9a-44612af115ac",
"metadata": {},
"outputs": [],
"source": [
"pfo.wrapper.grouper.get_index()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "502b18b1-498a-459e-a11e-30b5e895e2ad",
"metadata": {},
"outputs": [],
"source": [
"pfo.wrapper.columns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64d0fa79-4c39-4b30-99e9-afeb05f496e0",
"metadata": {},
"outputs": [],
"source": [
"pfo[(\"3M\", \"w2\")].stats()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1b03be1e-b989-4821-bd90-e4a4f9b9fd14",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" const_allocate_func,\n",
" group_configs=[\n",
" dict(args=([0.5, 0.2, 0.1, 0.1, 0.1],), every=\"1M\"),\n",
" dict(args=([0.2, 0.1, 0.1, 0.1, 0.5],), every=\"2M\"),\n",
" dict(args=([0.1, 0.1, 0.1, 0.5, 0.2],), every=\"3M\"),\n",
" dict(args=([0.1, 0.1, 0.5, 0.2, 0.1],), every=\"1M\"),\n",
" dict(args=([0.1, 0.5, 0.2, 0.1, 0.1],), every=\"2M\"),\n",
" dict(args=([0.5, 0.2, 0.1, 0.1, 0.1],), every=\"3M\"),\n",
" ]\n",
")\n",
"pfo.wrapper.grouper.get_index()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "286c19a7-487d-4461-9ac7-505408df2d00",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" const_allocate_func,\n",
" group_configs=[\n",
" dict(\n",
" allocate_func=const_allocate_func, \n",
" args=([0.5, 0.2, 0.1, 0.1, 0.1],),\n",
" _name=\"const\"\n",
" ),\n",
" dict(\n",
" allocate_func=random_allocate_func,\n",
" every=\"M\",\n",
" _name=\"random\"\n",
" ),\n",
" ]\n",
")\n",
"pfo.wrapper.grouper.get_index()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1370874-9993-4eb6-9561-8d4bdedcb93a",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" const_allocate_func,\n",
" group_configs={\n",
" \"const\": dict(\n",
" allocate_func=const_allocate_func, \n",
" args=([0.5, 0.2, 0.1, 0.1, 0.1],)\n",
" ),\n",
" \"random\": dict(\n",
" allocate_func=random_allocate_func,\n",
" ),\n",
" },\n",
" every=vbt.Param([\"1M\", \"2M\", \"3M\"])\n",
")\n",
"pfo.wrapper.grouper.get_index()"
]
},
{
"cell_type": "markdown",
"id": "084666b9-43f5-459a-950c-12092771572e",
"metadata": {},
"source": [
"#### Numba"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1540dfe-a610-4cec-9acc-6f8f88585f76",
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def rotation_allocate_func_nb(i, idx, n_cols):\n",
" weights = np.full(n_cols, 0)\n",
" weights[i % n_cols] = 1\n",
" return weights\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" rotation_allocate_func_nb,\n",
" vbt.RepEval(\"len(wrapper.columns)\"),\n",
" every=\"W\",\n",
" jitted_loop=True\n",
")\n",
"\n",
"print(pfo.allocations.head())"
]
},
{
"cell_type": "markdown",
"id": "d6d5353d-99bd-4d8e-8bdd-fa8f32759b87",
"metadata": {},
"source": [
"#### Distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fdc416bb-0457-4cb6-8838-9d9fce2e7551",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" rotation_allocate_func_nb,\n",
" vbt.Rep(\"i\"),\n",
" vbt.Rep(\"index_point\"),\n",
" vbt.RepEval(\"len(wrapper.columns)\"),\n",
" every=\"D\",\n",
" execute_kwargs=dict(engine=\"dask\")\n",
")\n",
"\n",
"print(pfo.allocations.head())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "88153e42-fbfa-47b9-bdc0-31c786948fa2",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" rotation_allocate_func_nb,\n",
" vbt.RepEval(\"len(wrapper.columns)\"),\n",
" every=\"D\",\n",
" jitted_loop=True,\n",
" chunked=dict(\n",
" arg_take_spec=dict(args=vbt.ArgsTaker(None)),\n",
" engine=\"dask\"\n",
" )\n",
")\n",
"\n",
"print(pfo.allocations.head())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53abd71a-db25-46d9-b826-e4424bd713a9",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_allocate_func(\n",
" symbol_wrapper,\n",
" rotation_allocate_func_nb,\n",
" vbt.RepEval(\"len(wrapper.columns)\"),\n",
" every=\"D\",\n",
" jitted_loop=True,\n",
" jitted=dict(parallel=True)\n",
")\n",
"\n",
"print(pfo.allocations.head())"
]
},
{
"cell_type": "markdown",
"id": "9c40824e-6c4c-4ba9-a01e-0288132fbccd",
"metadata": {},
"source": [
"## Optimization"
]
},
{
"cell_type": "markdown",
"id": "4fc8fe78-7c3a-458d-8c71-fcb0697ab748",
"metadata": {},
"source": [
"### Index ranges"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37bd154b-352e-431c-9958-276193afac4f",
"metadata": {},
"outputs": [],
"source": [
"example_ranges = data.wrapper.get_index_ranges(every=\"M\")\n",
"example_ranges[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8ce4b815-90d1-4c66-a257-c7ca854375e1",
"metadata": {},
"outputs": [],
"source": [
"example_ranges[1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca7a3e1a-c959-4ec1-a39c-24b03bc77978",
"metadata": {},
"outputs": [],
"source": [
"data.wrapper.index[example_ranges[0][0]:example_ranges[1][0]]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f655f008-a2b6-4861-b0b9-6eacc712f3a9",
"metadata": {},
"outputs": [],
"source": [
"example_ranges = data.wrapper.get_index_ranges(\n",
" every=\"M\", \n",
" lookback_period=\"3M\"\n",
")\n",
"\n",
"def get_index_bounds(range_starts, range_ends):\n",
" for i in range(len(range_starts)):\n",
" range_index = data.wrapper.index[range_starts[i]:range_ends[i]]\n",
" yield range_index[0], range_index[-1]\n",
"\n",
"list(get_index_bounds(*example_ranges))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "533a7760-bf7a-4e85-92c6-5905ee4e7402",
"metadata": {},
"outputs": [],
"source": [
"example_ranges = data.wrapper.get_index_ranges(\n",
" start=[\"2020-01-01\", \"2020-04-01\", \"2020-08-01\"],\n",
" end=[\"2020-04-01\", \"2020-08-01\", \"2020-12-01\"]\n",
")\n",
"\n",
"list(get_index_bounds(*example_ranges))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d990f69b-57e3-467a-96f7-4629e2efc3be",
"metadata": {},
"outputs": [],
"source": [
"example_ranges = data.wrapper.get_index_ranges(\n",
" start=\"2020-01-01\",\n",
" end=[\"2020-04-01\", \"2020-08-01\", \"2020-12-01\"]\n",
")\n",
"\n",
"list(get_index_bounds(*example_ranges))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf3e9132-1ad9-419c-a947-28d76605f0a1",
"metadata": {},
"outputs": [],
"source": [
"example_ranges = data.wrapper.get_index_ranges(\n",
" every=\"Q\",\n",
" exact_start=True,\n",
" fixed_start=True\n",
")\n",
"\n",
"list(get_index_bounds(*example_ranges))"
]
},
{
"cell_type": "markdown",
"id": "4eaa14c9-abd5-47c5-ab3e-4a567798f4d6",
"metadata": {},
"source": [
"### Optimization method"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "65f35ec4-46c1-4402-a74c-39a7ecf44d50",
"metadata": {},
"outputs": [],
"source": [
"def inv_rank_optimize_func(price, index_slice):\n",
" price_period = price.iloc[index_slice]\n",
" first_price = price_period.iloc[0]\n",
" last_price = price_period.iloc[-1]\n",
" ret = (last_price - first_price) / first_price\n",
" ranks = ret.rank(ascending=False)\n",
" return ranks / ranks.sum()\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_optimize_func(\n",
" symbol_wrapper,\n",
" inv_rank_optimize_func,\n",
" data.get(\"Close\"),\n",
" vbt.Rep(\"index_slice\"),\n",
" every=\"M\"\n",
")\n",
"\n",
"print(pfo.allocations)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da8c39d5-31da-44cc-a384-0e270e8faea9",
"metadata": {},
"outputs": [],
"source": [
"print(pfo.alloc_records.records_readable)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7d2e0254-6445-4bde-a162-99f77ed07e06",
"metadata": {},
"outputs": [],
"source": [
"start_idx = pfo.alloc_records.values[0][\"start_idx\"]\n",
"end_idx = pfo.alloc_records.values[0][\"end_idx\"]\n",
"close_period = data.get(\"Close\").iloc[start_idx:end_idx]\n",
"close_period.vbt.rebase(1).vbt.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f8f395a-a4bc-4d82-bb1e-ceb838c1dbd9",
"metadata": {},
"outputs": [],
"source": [
"pfo.stats()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1bec9bf7-116d-4ee0-8f22-f5d85abb02be",
"metadata": {},
"outputs": [],
"source": [
"pfo.plots().show_svg()"
]
},
{
"cell_type": "markdown",
"id": "4cd5f118-7eff-444f-bcad-b0caed5563ac",
"metadata": {},
"source": [
"#### Numba"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e7d1b93-cbe0-489a-833e-2421cdddf5cd",
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def inv_rank_optimize_func_nb(i, start_idx, end_idx, price):\n",
" price_period = price[start_idx:end_idx]\n",
" first_price = price_period[0]\n",
" last_price = price_period[-1]\n",
" ret = (last_price - first_price) / first_price\n",
" ranks = vbt.nb.rank_1d_nb(-ret)\n",
" return ranks / ranks.sum()\n",
"\n",
"pfo = vbt.PortfolioOptimizer.from_optimize_func(\n",
" symbol_wrapper,\n",
" inv_rank_optimize_func_nb,\n",
" data.get(\"Close\").values,\n",
" every=\"M\",\n",
" jitted_loop=True\n",
")\n",
"\n",
"print(pfo.allocations)"
]
},
{
"cell_type": "markdown",
"id": "0f1cd1a0-beda-4a1d-ba6e-be353116c2a5",
"metadata": {},
"source": [
"## Integrations"
]
},
{
"cell_type": "markdown",
"id": "5b5eeddd-d48c-435e-afcd-032d56028605",
"metadata": {},
"source": [
"### PyPortfolioOpt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eddbb356-a684-43cf-bfaf-3a024b297532",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt.expected_returns import mean_historical_return\n",
"from pypfopt.risk_models import CovarianceShrinkage\n",
"from pypfopt.efficient_frontier import EfficientFrontier\n",
"\n",
"expected_returns = mean_historical_return(data.get(\"Close\"))\n",
"cov_matrix = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"optimizer = EfficientFrontier(expected_returns, cov_matrix)\n",
"weights = optimizer.max_sharpe()\n",
"weights"
]
},
{
"cell_type": "markdown",
"id": "9f718768-cf18-44fc-af63-ac18b28312c7",
"metadata": {},
"source": [
"#### Parsing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d01cc1c-99f3-47b6-91ef-5781d5ce2de0",
"metadata": {},
"outputs": [],
"source": [
"from vectorbtpro.portfolio.pfopt.base import resolve_pypfopt_func_kwargs\n",
"\n",
"print(vbt.format_func(mean_historical_return))\n",
"\n",
"print(vbt.prettify(resolve_pypfopt_func_kwargs(\n",
" mean_historical_return, \n",
" prices=data.get(\"Close\"),\n",
" freq=\"1h\",\n",
" year_freq=\"365d\",\n",
" other_arg=100\n",
")))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42341016-70d0-4ba1-b386-97b0ca8a3422",
"metadata": {},
"outputs": [],
"source": [
"print(vbt.prettify(resolve_pypfopt_func_kwargs(\n",
" EfficientFrontier, \n",
" prices=data.get(\"Close\")\n",
")))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd9d9605-e5db-4466-af74-25a4f8fe7eae",
"metadata": {},
"outputs": [],
"source": [
"print(vbt.prettify(resolve_pypfopt_func_kwargs(\n",
" EfficientFrontier, \n",
" prices=data.get(\"Close\"),\n",
" expected_returns=\"ema_historical_return\",\n",
" cov_matrix=\"sample_cov\"\n",
")))"
]
},
{
"cell_type": "markdown",
"id": "24a6eef0-9743-4a77-9966-9f049ecf3bc1",
"metadata": {},
"source": [
"#### Auto-optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c5a41b5-0c06-445e-8279-fab28bd64ee2",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(prices=data.get(\"Close\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "999ff2a1-57db-4f25-817f-296e3323b65e",
"metadata": {},
"outputs": [],
"source": [
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"ef = EfficientFrontier(None, S, weight_bounds=(-1, 1))\n",
"ef.min_volatility()\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45ed42f6-34e7-481a-b1bd-3b3d8e45ca9c",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" expected_returns=None,\n",
" weight_bounds=(-1, 1),\n",
" target=\"min_volatility\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6ed4f49-2c33-4a12-9c6b-f91dfa7d381b",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt.expected_returns import capm_return\n",
"\n",
"sector_mapper = {\n",
" \"ADAUSDT\": \"DeFi\",\n",
" \"BNBUSDT\": \"DeFi\",\n",
" \"BTCUSDT\": \"Payment\",\n",
" \"ETHUSDT\": \"DeFi\",\n",
" \"XRPUSDT\": \"Payment\"\n",
"}\n",
"sector_lower = {\n",
" \"DeFi\": 0.75\n",
"}\n",
"sector_upper = {}\n",
"\n",
"mu = capm_return(data.get(\"Close\"))\n",
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"ef = EfficientFrontier(mu, S)\n",
"ef.add_sector_constraints(sector_mapper, sector_lower, sector_upper)\n",
"adausdt_index = ef.tickers.index(\"ADAUSDT\")\n",
"ef.add_constraint(lambda w: w[adausdt_index] == 0.10)\n",
"ef.max_sharpe()\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0920cb2-fcec-4d60-bbd1-860ff7e9f3e0",
"metadata": {},
"outputs": [],
"source": [
"adausdt_index = list(sector_mapper.keys()).index(\"ADAUSDT\")\n",
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" sector_mapper=sector_mapper,\n",
" sector_lower=sector_lower,\n",
" sector_upper=sector_upper,\n",
" constraints=[lambda w: w[adausdt_index] == 0.10],\n",
" expected_returns=\"capm_return\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3baebc8f-b29e-4b99-924d-be1e30c41819",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt.objective_functions import L2_reg\n",
"\n",
"mu = capm_return(data.get(\"Close\"))\n",
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"ef = EfficientFrontier(mu, S)\n",
"ef.add_sector_constraints(sector_mapper, sector_lower, sector_upper)\n",
"ef.add_objective(L2_reg, gamma=0.1) # gamme is the tuning parameter\n",
"ef.efficient_risk(0.15)\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "962886c1-70ed-4537-bce5-4329f47eeb66",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" expected_returns=\"capm_return\",\n",
" sector_mapper=sector_mapper,\n",
" sector_lower=sector_lower,\n",
" sector_upper=sector_upper,\n",
" objectives=[\"L2_reg\"],\n",
" gamma=0.1,\n",
" target=\"efficient_risk\",\n",
" target_volatility=0.15\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7d0be75d-a98c-476d-8355-d4d9682ca44c",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt import EfficientSemivariance\n",
"from pypfopt.expected_returns import returns_from_prices\n",
"\n",
"mu = capm_return(data.get(\"Close\"))\n",
"returns = returns_from_prices(data.get(\"Close\"))\n",
"returns = returns.dropna()\n",
"es = EfficientSemivariance(mu, returns)\n",
"es.efficient_return(0.01)\n",
"weights = es.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ec40502-5d85-477f-a719-1d81626b308f",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" expected_returns=\"capm_return\",\n",
" optimizer=\"efficient_semivariance\",\n",
" target=\"efficient_return\",\n",
" target_return=0.01\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc19d54f-e653-46b7-9dc1-e9164a6cafb6",
"metadata": {},
"outputs": [],
"source": [
"initial_weights = np.array([1 / len(data.symbols)] * len(data.symbols))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c1ca5f3d-717b-48de-8ca6-7365785314ad",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt.objective_functions import transaction_cost\n",
"\n",
"mu = mean_historical_return(data.get(\"Close\"))\n",
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"ef = EfficientFrontier(mu, S)\n",
"ef.add_objective(transaction_cost, w_prev=initial_weights, k=0.001)\n",
"ef.add_objective(L2_reg, gamma=0.05)\n",
"ef.min_volatility()\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2872ca4e-3e6c-4013-9f9e-6a78210da576",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" objectives=[\"transaction_cost\", \"L2_reg\"],\n",
" w_prev=initial_weights, \n",
" k=0.001,\n",
" gamma=0.05,\n",
" target=\"min_volatility\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "93a2b1f9-3b76-4b73-8875-2f8148aed53e",
"metadata": {},
"outputs": [],
"source": [
"import cvxpy as cp\n",
"\n",
"def logarithmic_barrier_objective(w, cov_matrix, k=0.1):\n",
" log_sum = cp.sum(cp.log(w))\n",
" var = cp.quad_form(w, cov_matrix)\n",
" return var - k * log_sum"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "618445c6-b151-4e48-ab97-09e73ea454ab",
"metadata": {},
"outputs": [],
"source": [
"mu = mean_historical_return(data.get(\"Close\"))\n",
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"ef = EfficientFrontier(mu, S, weight_bounds=(0.01, 0.3))\n",
"ef.convex_objective(logarithmic_barrier_objective, cov_matrix=S, k=0.001)\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "363b4772-b8f7-4780-8dd6-0879ca0be1e4",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" weight_bounds=(0.01, 0.3),\n",
" k=0.001,\n",
" target=logarithmic_barrier_objective\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d315ee2a-a300-4b2f-aabf-8dfd8e8a71ea",
"metadata": {},
"outputs": [],
"source": [
"def deviation_risk_parity(w, cov_matrix):\n",
" cov_matrix = np.asarray(cov_matrix)\n",
" n = cov_matrix.shape[0]\n",
" rp = (w * (cov_matrix @ w)) / cp.quad_form(w, cov_matrix)\n",
" return cp.sum_squares(rp - 1 / n).value"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "becea885-c1a8-4346-aa21-49e426fd6a30",
"metadata": {},
"outputs": [],
"source": [
"mu = mean_historical_return(data.get(\"Close\"))\n",
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"ef = EfficientFrontier(mu, S)\n",
"ef.nonconvex_objective(deviation_risk_parity, ef.cov_matrix)\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d23a8f29-5fcb-489f-a566-d5e303009009",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" target=deviation_risk_parity,\n",
" target_is_convex=False\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52331cdc-4078-4d33-b6a5-c65f85afc1c1",
"metadata": {},
"outputs": [],
"source": [
"sp500_data = vbt.YFData.pull(\n",
" \"^GSPC\", \n",
" start=data.wrapper.index[0], \n",
" end=data.wrapper.index[-1]\n",
")\n",
"market_caps = data.get(\"Close\") * data.get(\"Volume\")\n",
"viewdict = {\n",
" \"ADAUSDT\": 0.20, \n",
" \"BNBUSDT\": -0.30, \n",
" \"BTCUSDT\": 0, \n",
" \"ETHUSDT\": -0.2, \n",
" \"XRPUSDT\": 0.15\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "953df670-d6bc-4530-a5a7-4e6120fc64d8",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt.black_litterman import (\n",
" market_implied_risk_aversion,\n",
" market_implied_prior_returns,\n",
" BlackLittermanModel\n",
")\n",
"\n",
"S = CovarianceShrinkage(data.get(\"Close\")).ledoit_wolf()\n",
"delta = market_implied_risk_aversion(sp500_data.get(\"Close\"))\n",
"prior = market_implied_prior_returns(market_caps.iloc[-1], delta, S)\n",
"bl = BlackLittermanModel(S, pi=prior, absolute_views=viewdict)\n",
"rets = bl.bl_returns()\n",
"ef = EfficientFrontier(rets, S)\n",
"ef.min_volatility()\n",
"weights = ef.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "48480f30-3641-463f-8a8e-5ce0485d1795",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" expected_returns=\"bl_returns\",\n",
" market_prices=sp500_data.get(\"Close\"),\n",
" market_caps=market_caps.iloc[-1],\n",
" absolute_views=viewdict,\n",
" target=\"min_volatility\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "597a8769-bf4c-49ee-8af6-e15db567d9e2",
"metadata": {},
"outputs": [],
"source": [
"from pypfopt import HRPOpt\n",
"\n",
"rets = returns_from_prices(data.get(\"Close\"))\n",
"hrp = HRPOpt(rets)\n",
"hrp.optimize()\n",
"weights = hrp.clean_weights()\n",
"weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "04648737-d27f-4c57-96a7-7a898b7bbf30",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize(\n",
" prices=data.get(\"Close\"),\n",
" optimizer=\"hrp\",\n",
" target=\"optimize\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "7ed59e17-aff0-4d23-b896-bce0a4d45fef",
"metadata": {},
"source": [
"#### Argument groups"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ddbad7dd-8655-40fa-8b0a-d85deb808cec",
"metadata": {},
"outputs": [],
"source": [
"vbt.pypfopt_optimize( \n",
" prices=data.get(\"Close\"),\n",
" expected_returns=\"bl_returns\", \n",
" market_prices=sp500_data.get(\"Close\"),\n",
" market_caps=market_caps.iloc[-1],\n",
" absolute_views=viewdict,\n",
" target=\"min_volatility\",\n",
" cov_matrix=vbt.pfopt_func_dict({\n",
" \"EfficientFrontier\": \"sample_cov\",\n",
" \"_def\": \"ledoit_wolf\"\n",
" })\n",
")"
]
},
{
"cell_type": "markdown",
"id": "5afd87a6-eaa1-4656-9e09-f988f811c139",
"metadata": {},
"source": [
"#### Periodically"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "69629e9e-dcff-48a0-9af6-6ccef81d1dce",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_pypfopt(\n",
" prices=data.get(\"Close\"),\n",
" every=\"W\"\n",
")\n",
"\n",
"pfo.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "719a764c-a500-41ad-b93b-2161b618373b",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_pypfopt(\n",
" prices=data.get(\"Close\"),\n",
" every=\"W\",\n",
" target=vbt.Param([\n",
" \"max_sharpe\", \n",
" \"min_volatility\", \n",
" \"max_quadratic_utility\"\n",
" ])\n",
")\n",
"\n",
"pfo.plot(column=\"min_volatility\").show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91d13211-9be7-4bd6-9357-23fa3320ffa4",
"metadata": {},
"outputs": [],
"source": [
"pf = pfo.simulate(data, freq=\"1h\")\n",
"\n",
"pf.sharpe_ratio"
]
},
{
"cell_type": "markdown",
"id": "b1eb1e34-c040-49ab-96c5-2fe596444b50",
"metadata": {},
"source": [
"### Riskfolio-Lib"
]
},
{
"cell_type": "markdown",
"id": "2177d3d7-963e-4e88-a20d-ff649699ff00",
"metadata": {},
"source": [
"#### Parsing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bcbe3a9a-1473-4571-8a8f-ca32815ede4f",
"metadata": {},
"outputs": [],
"source": [
"import riskfolio as rp\n",
"\n",
"returns = data.get(\"Close\").vbt.to_returns()\n",
"\n",
"port = rp.Portfolio(returns=returns)\n",
"port.assets_stats(\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\",\n",
" d=0.94\n",
")\n",
"w = port.optimization(\n",
" model=\"Classic\",\n",
" rm=\"MV\",\n",
" obj=\"Sharpe\",\n",
" rf=0,\n",
" l=0,\n",
" hist=True\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d0c602c-0ec9-45ac-b663-9cff902154bf",
"metadata": {},
"outputs": [],
"source": [
"from vectorbtpro.utils.parsing import get_func_arg_names\n",
"\n",
"get_func_arg_names(port.assets_stats)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44b61c1c-d5b8-4fed-a994-df2cb018c820",
"metadata": {},
"outputs": [],
"source": [
"from vectorbtpro.portfolio.pfopt.base import resolve_riskfolio_func_kwargs\n",
"\n",
"resolve_riskfolio_func_kwargs(\n",
" port.assets_stats,\n",
" method_mu=\"hist\",\n",
" method_cov=\"hist\",\n",
" model=\"Classic\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9b0e924-0a7e-4241-9fb1-cf4ae622c718",
"metadata": {},
"outputs": [],
"source": [
"resolve_riskfolio_func_kwargs(\n",
" port.assets_stats,\n",
" method_mu=\"hist\",\n",
" method_cov=\"hist\",\n",
" model=\"Classic\",\n",
" func_kwargs=dict(\n",
" assets_stats=dict(method_mu=\"ewma1\"),\n",
" optimization=dict(model=\"BL\")\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "acc762cc-a303-4a17-8056-d6d387ce7949",
"metadata": {},
"source": [
"#### Auto-optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d77e4539-e19d-4496-9694-8fd67ee3f753",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(returns)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6a920e0-080a-4663-ad16-fd2e3de42e39",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)\n",
"port.assets_stats(\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" d=0.94\n",
")\n",
"w = port.optimization(\n",
" model=\"Classic\", \n",
" rm=\"UCI\", \n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" hist=True\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0cc3ab5a-e304-4f44-9cc3-05f458d7818b",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" d=0.94,\n",
" rm=\"UCI\", \n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" hist=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5cafb960-87ab-4aaf-818a-085270ade0db",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)\n",
"port.assets_stats(\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" d=0.94\n",
")\n",
"port.wc_stats(\n",
" box=\"s\", \n",
" ellip=\"s\", \n",
" q=0.05, \n",
" n_sim=3000, \n",
" window=3, \n",
" dmu=0.1, \n",
" dcov=0.1, \n",
" seed=0\n",
")\n",
"w = port.wc_optimization(\n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" Umu=\"box\", \n",
" Ucov=\"box\"\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "edf7699f-b286-4b00-b3a9-244641840cf1",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" opt_method=\"wc\",\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" box=\"s\", \n",
" ellip=\"s\", \n",
" q=0.05, \n",
" n_sim=3000, \n",
" window=3, \n",
" dmu=0.1, \n",
" dcov=0.1, \n",
" seed=0,\n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" Umu=\"box\", \n",
" Ucov=\"box\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c175c0b-d47b-4039-9918-a4d496b941be",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" func_kwargs=dict(\n",
" assets_stats=dict(\n",
" opt_method=\"wc\",\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\"\n",
" ),\n",
" wc_stats=dict(\n",
" box=\"s\", \n",
" ellip=\"s\", \n",
" q=0.05, \n",
" n_sim=3000, \n",
" window=3, \n",
" dmu=0.1, \n",
" dcov=0.1, \n",
" seed=0\n",
" ),\n",
" wc_optimization=dict(\n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" Umu=\"box\", \n",
" Ucov=\"box\"\n",
" )\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "558d4ffc-9297-497a-bf76-0c5114696d00",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)\n",
"port.sht = True # Allows to use Short Weights\n",
"port.uppersht = 0.3 # Maximum value of sum of short weights in absolute value\n",
"port.upperlng = 1.3 # Maximum value of sum of positive weights\n",
"port.budget = 1.0 # No leverage\n",
"port.assets_stats(\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" d=0.94\n",
")\n",
"w = port.optimization(\n",
" model=\"Classic\", \n",
" rm=\"MV\", \n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" hist=True\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0343022a-98e1-44e1-82c6-61c7d2175cfc",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" sht=True,\n",
" uppersht=0.3,\n",
" upperlng=1.3,\n",
" budget=1.0,\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" d=0.94,\n",
" rm=\"MV\", \n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" hist=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "edd1993f-27de-4b36-9c2d-fb801f145e3e",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)\n",
"port.assets_stats(\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\",\n",
" d=0.94\n",
")\n",
"asset_classes = {\"Assets\": returns.columns.tolist()}\n",
"asset_classes = pd.DataFrame(asset_classes)\n",
"constraints = {\n",
" \"Disabled\": [False, False],\n",
" \"Type\": [\"All Assets\", \"Assets\"],\n",
" \"Set\": [\"\", \"\"],\n",
" \"Position\": [\"\", \"BTCUSDT\"],\n",
" \"Sign\": [\">=\", \"<=\"],\n",
" 'Weight': [0.1, 0.15],\n",
" \"Type Relative\": [\"\", \"\"],\n",
" \"Relative Set\": [\"\", \"\"],\n",
" \"Relative\": [\"\", \"\"],\n",
" \"Factor\": [\"\", \"\"],\n",
"}\n",
"constraints = pd.DataFrame(constraints)\n",
"A, B = rp.assets_constraints(constraints, asset_classes)\n",
"port.ainequality = A\n",
"port.binequality = B\n",
"w = port.optimization(\n",
" model=\"Classic\",\n",
" rm=\"MV\",\n",
" obj=\"Sharpe\",\n",
" rf=0,\n",
" l=0,\n",
" hist=True\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d782230-ca6c-4816-b398-0a42510bf4f5",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" constraints=[{\n",
" \"Type\": \"All Assets\",\n",
" \"Sign\": \">=\",\n",
" \"Weight\": 0.1\n",
" }, {\n",
" \"Type\": \"Assets\",\n",
" \"Position\": \"BTCUSDT\",\n",
" \"Sign\": \"<=\",\n",
" \"Weight\": 0.15\n",
" }],\n",
" d=0.94,\n",
" rm=\"MV\", \n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" hist=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37b200ad-f197-4b83-ae03-2bb7ff1fc21b",
"metadata": {},
"outputs": [],
"source": [
"tags = [\n",
" \"Smart contracts\",\n",
" \"Smart contracts\",\n",
" \"Payments\",\n",
" \"Smart contracts\",\n",
" \"Payments\"\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7db1f74f-4fc2-455e-bd4b-e42f8e0f733b",
"metadata": {},
"outputs": [],
"source": [
"port = rp.Portfolio(returns=returns)\n",
"port.assets_stats(\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\",\n",
" d=0.94\n",
")\n",
"asset_classes = {\n",
" \"Assets\": returns.columns.tolist(),\n",
" \"Tags\": tags\n",
"}\n",
"asset_classes = pd.DataFrame(asset_classes)\n",
"constraints = {\n",
" \"Disabled\": [False],\n",
" \"Type\": [\"Classes\"],\n",
" \"Set\": [\"Tags\"],\n",
" \"Position\": [\"Smart contracts\"],\n",
" \"Sign\": [\">=\"],\n",
" 'Weight': [0.8],\n",
" \"Type Relative\": [\"\"],\n",
" \"Relative Set\": [\"\"],\n",
" \"Relative\": [\"\"],\n",
" \"Factor\": [\"\"],\n",
"}\n",
"constraints = pd.DataFrame(constraints)\n",
"A, B = rp.assets_constraints(constraints, asset_classes)\n",
"port.ainequality = A\n",
"port.binequality = B\n",
"w = port.optimization(\n",
" model=\"Classic\",\n",
" rm=\"MV\",\n",
" obj=\"Sharpe\",\n",
" rf=0,\n",
" l=0,\n",
" hist=True\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7403aeb2-fae6-487d-ae11-f50071dd0e1a",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" method_mu=\"hist\", \n",
" method_cov=\"hist\", \n",
" asset_classes={\"Tags\": tags},\n",
" constraints=[{\n",
" \"Type\": \"Classes\",\n",
" \"Set\": \"Tags\",\n",
" \"Position\": \"Smart contracts\",\n",
" \"Sign\": \">=\",\n",
" \"Weight\": 0.8\n",
" }],\n",
" d=0.94,\n",
" rm=\"MV\", \n",
" obj=\"Sharpe\", \n",
" rf=0, \n",
" l=0, \n",
" hist=True\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ee363b9-7241-4569-8c90-f1f0feb98644",
"metadata": {},
"outputs": [],
"source": [
"port = rp.HCPortfolio(returns=returns)\n",
"w = port.optimization(\n",
" model=\"NCO\",\n",
" codependence=\"pearson\",\n",
" covariance=\"hist\",\n",
" obj=\"MinRisk\",\n",
" rm=\"MV\",\n",
" rf=0,\n",
" l=2,\n",
" linkage=\"ward\",\n",
" max_k=10,\n",
" leaf_order=True\n",
")\n",
"print(w.T)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "02c18d54-6721-49d8-a7de-fb3bd44a2dd9",
"metadata": {},
"outputs": [],
"source": [
"vbt.riskfolio_optimize(\n",
" returns,\n",
" port_cls=\"HCPortfolio\",\n",
" model=\"NCO\",\n",
" codependence=\"pearson\",\n",
" covariance=\"hist\",\n",
" obj=\"MinRisk\",\n",
" rm=\"MV\",\n",
" rf=0,\n",
" l=2,\n",
" linkage=\"ward\",\n",
" max_k=10,\n",
" leaf_order=True\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c329e926-3cea-4610-b214-0d01c2001a7f",
"metadata": {},
"source": [
"#### Periodically"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25c31c61-5735-4312-b3a0-f0d1513bcd4d",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_riskfolio(\n",
" returns=returns,\n",
" every=\"W\"\n",
")\n",
"\n",
"pfo.plot().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a538f664-dabe-4820-ae5b-0984df69f81c",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer.from_riskfolio(\n",
" returns=returns,\n",
" constraints=[{\n",
" \"Type\": \"Assets\",\n",
" \"Position\": \"BTCUSDT\",\n",
" \"Sign\": \"<=\",\n",
" \"Weight\": vbt.Param([0.1, 0.2, 0.3], name=\"BTCUSDT_maxw\")\n",
" }],\n",
" every=\"W\",\n",
" param_search_kwargs=dict(incl_types=list)\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0835ab3e-b051-4a26-aa2b-a69ca9000d18",
"metadata": {},
"outputs": [],
"source": [
"print(pfo.allocations.groupby(\"BTCUSDT_maxw\").max())"
]
},
{
"cell_type": "markdown",
"id": "bf1c570e-764f-4c9a-be82-fdf1d0e2ae51",
"metadata": {},
"source": [
"### Universal portfolios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8dcf11ca-dc92-4637-aab4-cffc2aa7b8cb",
"metadata": {},
"outputs": [],
"source": [
"from universal import tools, algos\n",
"\n",
"with vbt.WarningsFiltered():\n",
" algo = algos.CRP()\n",
" algo_result = algo.run(data.get(\"Close\"))\n",
" \n",
"print(algo_result.weights)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d73a4383-09cd-43a1-9306-9585cf05d3ce",
"metadata": {},
"outputs": [],
"source": [
"with vbt.WarningsFiltered():\n",
" algo = algos.DynamicCRP(\n",
" n=30, \n",
" min_history=7, \n",
" metric='sharpe', \n",
" alpha=0.01\n",
" )\n",
" algo_result = algo.run(data.get(\"Close\").resample(\"D\").last())\n",
" down_weights = algo_result.weights\n",
" \n",
"print(down_weights)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "933da56f-6797-4d05-91cb-f5071a262058",
"metadata": {},
"outputs": [],
"source": [
"weights = down_weights.vbt.realign(\n",
" data.wrapper.index,\n",
" freq=\"1h\",\n",
" source_rbound=True,\n",
" target_rbound=True,\n",
" ffill=False\n",
")\n",
"print(weights)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d40174db-9aa1-4397-b76e-f3dfd1f5db5a",
"metadata": {},
"outputs": [],
"source": [
"with vbt.WarningsFiltered():\n",
" down_pfo = vbt.PortfolioOptimizer.from_universal_algo(\n",
" \"DynamicCRP\",\n",
" data.get(\"Close\").resample(\"D\").last(),\n",
" n=vbt.Param([7, 14, 30, 90]), \n",
" min_history=7, \n",
" metric='sharpe', \n",
" alpha=0.01\n",
" )\n",
"\n",
"down_pfo.plot(column=90).show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "248bb47a-28d1-47e5-8c25-a4807c7d6c8b",
"metadata": {},
"outputs": [],
"source": [
"resampler = vbt.Resampler(\n",
" down_pfo.wrapper.index, \n",
" data.wrapper.index, \n",
" target_freq=\"1h\"\n",
")\n",
"pfo = down_pfo.resample(resampler)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "251b6943-4f95-4de9-914c-f3b5c3d326d6",
"metadata": {},
"outputs": [],
"source": [
"pf = pfo.simulate(data, freq=\"1h\")\n",
"\n",
"pf.sharpe_ratio"
]
},
{
"cell_type": "markdown",
"id": "447827ee-687d-4b7b-ae6b-c4b7e2900b22",
"metadata": {},
"source": [
"#### Custom algorithm"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4e4aa1ef-0306-4d62-8ec8-24f02a72509e",
"metadata": {},
"outputs": [],
"source": [
"from universal.algo import Algo\n",
"\n",
"class MeanReversion(Algo):\n",
" PRICE_TYPE = 'log'\n",
" \n",
" def __init__(self, n):\n",
" self.n = n\n",
" super().__init__(min_history=n)\n",
" \n",
" def init_weights(self, cols):\n",
" return pd.Series(np.zeros(len(cols)), cols)\n",
" \n",
" def step(self, x, last_b, history):\n",
" ma = history.iloc[-self.n:].mean()\n",
" delta = x - ma\n",
" w = np.maximum(-delta, 0.)\n",
" return w / sum(w)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "32919021-27fb-4121-80c5-640bab1811f4",
"metadata": {},
"outputs": [],
"source": [
"with vbt.WarningsFiltered():\n",
" pfo = vbt.PortfolioOptimizer.from_universal_algo(\n",
" MeanReversion,\n",
" data.get(\"Close\").resample(\"D\").last(),\n",
" n=30,\n",
" every=\"W\"\n",
" )\n",
"\n",
"pfo.plot().show_svg()"
]
},
{
"cell_type": "markdown",
"id": "f5a0e81b-4b60-49d2-8a97-f61f3f937a76",
"metadata": {},
"source": [
"## Dynamic"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6939f3bf-7430-4e13-8194-dc687fc17f95",
"metadata": {},
"outputs": [],
"source": [
"GroupMemory = namedtuple(\"GroupMemory\", [\n",
" \"target_alloc\", \n",
" \"size_type\",\n",
" \"direction\",\n",
" \"order_value_out\"\n",
"])\n",
"\n",
"@njit\n",
"def pre_group_func_nb(c):\n",
" group_memory = GroupMemory(\n",
" target_alloc=np.full(c.group_len, np.nan),\n",
" size_type=np.full(c.group_len, vbt.pf_enums.SizeType.TargetPercent),\n",
" direction=np.full(c.group_len, vbt.pf_enums.Direction.Both),\n",
" order_value_out=np.full(c.group_len, np.nan)\n",
" )\n",
" return group_memory,\n",
"\n",
"@njit\n",
"def pre_segment_func_nb(\n",
" c, \n",
" group_memory,\n",
" min_history,\n",
" threshold,\n",
" allocate_func_nb,\n",
" *args\n",
"):\n",
" should_rebalance = False\n",
" \n",
" if c.i >= min_history:\n",
" in_position = False\n",
" for col in range(c.from_col, c.to_col):\n",
" if c.last_position[col] != 0:\n",
" in_position = True\n",
" break\n",
" \n",
" if not in_position:\n",
" should_rebalance = True\n",
" else:\n",
" curr_value = c.last_value[c.group]\n",
" for group_col in range(c.group_len):\n",
" col = c.from_col + group_col\n",
" curr_position = c.last_position[col]\n",
" curr_price = c.last_val_price[col]\n",
" curr_alloc = curr_position * curr_price / curr_value\n",
" curr_threshold = vbt.pf_nb.select_from_col_nb(c, col, threshold)\n",
" alloc_diff = curr_alloc - group_memory.target_alloc[group_col]\n",
" \n",
" if abs(alloc_diff) >= curr_threshold:\n",
" should_rebalance = True\n",
" break\n",
" \n",
" if should_rebalance:\n",
" allocate_func_nb(c, group_memory, *args)\n",
" vbt.pf_nb.sort_call_seq_1d_nb(\n",
" c, \n",
" group_memory.target_alloc, \n",
" group_memory.size_type, \n",
" group_memory.direction, \n",
" group_memory.order_value_out\n",
" )\n",
" \n",
" return group_memory, should_rebalance\n",
"\n",
"@njit\n",
"def order_func_nb(\n",
" c, \n",
" group_memory,\n",
" should_rebalance, \n",
" price,\n",
" fees\n",
"):\n",
" if not should_rebalance:\n",
" return vbt.pf_nb.order_nothing_nb()\n",
" \n",
" group_col = c.col - c.from_col\n",
" return vbt.pf_nb.order_nb(\n",
" size=group_memory.target_alloc[group_col], \n",
" price=vbt.pf_nb.select_nb(c, price),\n",
" size_type=group_memory.size_type[group_col],\n",
" direction=group_memory.direction[group_col],\n",
" fees=vbt.pf_nb.select_nb(c, fees)\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e55a608f-9ea4-46b3-bde3-188f20728530",
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def uniform_allocate_func_nb(c, group_memory):\n",
" for group_col in range(c.group_len):\n",
" group_memory.target_alloc[group_col] = 1 / c.group_len"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c90956d-24e0-4d42-93ff-19a3c3688288",
"metadata": {},
"outputs": [],
"source": [
"def simulate_threshold_rebalancing(threshold, allocate_func_nb, *args, **kwargs):\n",
" return vbt.Portfolio.from_order_func(\n",
" data.get(\"Close\"),\n",
" open=data.get(\"Open\"),\n",
" pre_group_func_nb=pre_group_func_nb, \n",
" pre_group_args=(),\n",
" pre_segment_func_nb=pre_segment_func_nb, \n",
" pre_segment_args=(\n",
" 0,\n",
" vbt.Rep(\"threshold\"),\n",
" allocate_func_nb,\n",
" *args\n",
" ),\n",
" order_func_nb=order_func_nb, \n",
" order_args=(vbt.Rep('price'), vbt.Rep('fees')),\n",
" broadcast_named_args=dict(\n",
" price=data.get(\"Close\"),\n",
" fees=0.005,\n",
" threshold=threshold\n",
" ),\n",
" cash_sharing=True,\n",
" group_by=vbt.ExceptLevel(\"symbol\"),\n",
" freq='1h', \n",
" **kwargs\n",
" )\n",
"\n",
"pf = simulate_threshold_rebalancing(0.05, uniform_allocate_func_nb)\n",
"pf.plot_allocations().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ccbf4e6-b3b6-4b9d-b036-03483440d176",
"metadata": {},
"outputs": [],
"source": [
"pf = simulate_threshold_rebalancing(\n",
" vbt.Param(np.arange(1, 16) / 100, name=\"threshold\"), \n",
" uniform_allocate_func_nb\n",
")\n",
"\n",
"pf.sharpe_ratio"
]
},
{
"cell_type": "markdown",
"id": "d0559a9a-d45a-4760-9d8d-4ee2cb8a3449",
"metadata": {},
"source": [
"### Post-analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "588f1e8b-5c2c-41b2-b50a-1d6b5d912963",
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def track_uniform_allocate_func_nb(c, group_memory, index_points, alloc_counter):\n",
" for group_col in range(c.group_len):\n",
" group_memory.target_alloc[group_col] = 1 / c.group_len\n",
" index_points[alloc_counter[0]] = c.i\n",
" alloc_counter[0] += 1\n",
"\n",
"index_points = np.empty(data.wrapper.shape[0], dtype=np.int_)\n",
"alloc_counter = np.full(1, 0)\n",
"pf = simulate_threshold_rebalancing(\n",
" 0.05,\n",
" track_uniform_allocate_func_nb, \n",
" index_points, \n",
" alloc_counter\n",
")\n",
"index_points = index_points[:alloc_counter[0]]\n",
"\n",
"data.wrapper.index[index_points]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2794df2a-d6b5-4b69-bfe4-765a06fe6200",
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def random_allocate_func_nb(\n",
" c, \n",
" group_memory, \n",
" alloc_points, \n",
" alloc_weights, \n",
" alloc_counter\n",
"):\n",
" weights = np.random.uniform(0, 1, c.group_len)\n",
" group_memory.target_alloc[:] = weights / weights.sum()\n",
" \n",
" group_count = alloc_counter[c.group]\n",
" count = alloc_counter.sum()\n",
" alloc_points[\"id\"][count] = group_count\n",
" alloc_points[\"col\"][count] = c.group\n",
" alloc_points[\"alloc_idx\"][count] = c.i\n",
" alloc_weights[count] = group_memory.target_alloc\n",
" alloc_counter[c.group] += 1\n",
"\n",
"thresholds = pd.Index(np.arange(1, 16) / 100, name=\"threshold\")\n",
"max_entries = data.wrapper.shape[0] * len(thresholds)\n",
"alloc_points = np.empty(max_entries, dtype=vbt.pf_enums.alloc_point_dt)\n",
"alloc_weights = np.empty((max_entries, len(data.symbols)), dtype=np.float_)\n",
"alloc_counter = np.full(len(thresholds), 0)\n",
"\n",
"pf = simulate_threshold_rebalancing(\n",
" vbt.Param(thresholds),\n",
" random_allocate_func_nb, \n",
" alloc_points, \n",
" alloc_weights,\n",
" alloc_counter,\n",
" seed=42\n",
")\n",
"alloc_points = alloc_points[:alloc_counter.sum()]\n",
"alloc_weights = alloc_weights[:alloc_counter.sum()]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d940d355-bce3-4443-857f-4ab62ed7aeb9",
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def random_allocate_func_nb(c, group_memory):\n",
" weights = np.random.uniform(0, 1, c.group_len)\n",
" group_memory.target_alloc[:] = weights / weights.sum()\n",
" \n",
" group_count = c.in_outputs.alloc_counter[c.group]\n",
" count = c.in_outputs.alloc_counter.sum()\n",
" c.in_outputs.alloc_points[\"id\"][count] = group_count\n",
" c.in_outputs.alloc_points[\"col\"][count] = c.group\n",
" c.in_outputs.alloc_points[\"alloc_idx\"][count] = c.i\n",
" c.in_outputs.alloc_weights[count] = group_memory.target_alloc\n",
" c.in_outputs.alloc_counter[c.group] += 1\n",
"\n",
"alloc_points = vbt.RepEval(\"\"\"\n",
" max_entries = target_shape[0] * len(group_lens)\n",
" np.empty(max_entries, dtype=alloc_point_dt)\n",
"\"\"\", context=dict(alloc_point_dt=vbt.pf_enums.alloc_point_dt))\n",
"alloc_weights = vbt.RepEval(\"\"\"\n",
" max_entries = target_shape[0] * len(group_lens)\n",
" np.empty((max_entries, n_cols), dtype=np.float_)\n",
"\"\"\", context=dict(n_cols=len(data.symbols)))\n",
"alloc_counter = vbt.RepEval(\"np.full(len(group_lens), 0)\")\n",
"\n",
"InOutputs = namedtuple(\"InOutputs\", [\n",
" \"alloc_points\",\n",
" \"alloc_weights\",\n",
" \"alloc_counter\"\n",
"])\n",
"in_outputs = InOutputs(\n",
" alloc_points=alloc_points, \n",
" alloc_weights=alloc_weights,\n",
" alloc_counter=alloc_counter,\n",
")\n",
"\n",
"pf = simulate_threshold_rebalancing(\n",
" vbt.Param(np.arange(1, 16) / 100, name=\"threshold\"),\n",
" random_allocate_func_nb, \n",
" in_outputs=in_outputs,\n",
" seed=42\n",
")\n",
"alloc_points = pf.in_outputs.alloc_points[:pf.in_outputs.alloc_counter.sum()]\n",
"alloc_weights = pf.in_outputs.alloc_weights[:pf.in_outputs.alloc_counter.sum()]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b1420356-030b-4815-a522-14ed1fe71642",
"metadata": {},
"outputs": [],
"source": [
"pfo = vbt.PortfolioOptimizer(\n",
" wrapper=pf.wrapper,\n",
" alloc_records=vbt.AllocPoints(\n",
" pf.wrapper.resolve(), \n",
" alloc_points\n",
" ),\n",
" allocations=alloc_weights\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fdf238e-e38b-4d6c-b36c-33ed342adccb",
"metadata": {},
"outputs": [],
"source": [
"print(pfo[0.1].allocations.describe())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "515f6baa-7b46-4f19-b84a-c9660ae95362",
"metadata": {},
"outputs": [],
"source": [
"pfo.plot(column=0.1).show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7466d4f5-c1c9-416e-a47d-802b3f6813ca",
"metadata": {},
"outputs": [],
"source": [
"pfo.plot(column=0.03).show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8671183-8dc7-4a33-8d0a-559ac876f0bd",
"metadata": {},
"outputs": [],
"source": [
"pf[0.03].plot_allocations().show_svg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "291d453b-1a17-436b-afbe-d3a8dfd8295c",
"metadata": {},
"outputs": [],
"source": [
"pf.sharpe_ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b09c8a6-1f02-4380-b972-f4fe4660a261",
"metadata": {},
"outputs": [],
"source": [
"pf_new = vbt.Portfolio.from_optimizer(\n",
" data,\n",
" pfo, \n",
" val_price=data.get(\"Open\"), \n",
" freq=\"1h\", \n",
" fees=0.005\n",
")\n",
"\n",
"pf_new.sharpe_ratio"
]
},
{
"cell_type": "markdown",
"id": "f4aabcbe-8baa-4507-a38a-a4b937d6a5c7",
"metadata": {},
"source": [
"### Bonus 1: Own optimizer"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de4de790-9fd4-4030-8dfb-dae986de9012",
"metadata": {},
"outputs": [],
"source": [
"@njit(nogil=True)\n",
"def optimize_portfolio_nb(\n",
" close, \n",
" val_price,\n",
" range_starts,\n",
" range_ends,\n",
" optimize_func_nb,\n",
" optimize_args=(),\n",
" price=np.inf,\n",
" fees=0.,\n",
" init_cash=100.,\n",
" group=0\n",
"):\n",
" val_price_ = vbt.to_2d_array_nb(np.asarray(val_price))\n",
" price_ = vbt.to_2d_array_nb(np.asarray(price))\n",
" fees_ = vbt.to_2d_array_nb(np.asarray(fees))\n",
"\n",
" order_records = np.empty(close.shape, dtype=vbt.pf_enums.order_dt)\n",
" order_counts = np.full(close.shape[1], 0, dtype=np.int_)\n",
" \n",
" order_value = np.empty(close.shape[1], dtype=np.float_)\n",
" call_seq = np.empty(close.shape[1], dtype=np.int_)\n",
" \n",
" last_position = np.full(close.shape[1], 0.0, dtype=np.float_)\n",
" last_debt = np.full(close.shape[1], 0.0, dtype=np.float_)\n",
" last_locked_cash = np.full(close.shape[1], 0.0, dtype=np.float_)\n",
" cash_now = float(init_cash)\n",
" free_cash_now = float(init_cash)\n",
" value_now = float(init_cash)\n",
"\n",
" for k in range(len(range_starts)):\n",
" i = range_ends[k]\n",
" size = optimize_func_nb(\n",
" range_starts[k], \n",
" range_ends[k], \n",
" *optimize_args\n",
" )\n",
" \n",
" value_now = cash_now\n",
" for col in range(close.shape[1]):\n",
" val_price_now = vbt.flex_select_nb(val_price_, i, col)\n",
" value_now += last_position[col] * val_price_now\n",
" \n",
" for col in range(close.shape[1]):\n",
" val_price_now = vbt.flex_select_nb(val_price_, i, col)\n",
" exec_state = vbt.pf_enums.ExecState(\n",
" cash=cash_now,\n",
" position=last_position[col],\n",
" debt=last_debt[col],\n",
" locked_cash=last_locked_cash[col],\n",
" free_cash=free_cash_now,\n",
" val_price=val_price_now,\n",
" value=value_now,\n",
" )\n",
" order_value[col] = vbt.pf_nb.approx_order_value_nb(\n",
" exec_state,\n",
" size[col],\n",
" vbt.pf_enums.SizeType.TargetPercent,\n",
" vbt.pf_enums.Direction.Both,\n",
" )\n",
" call_seq[col] = col\n",
"\n",
" vbt.pf_nb.insert_argsort_nb(order_value, call_seq)\n",
"\n",
" for c in range(close.shape[1]):\n",
" col = call_seq[c]\n",
" \n",
" order = vbt.pf_nb.order_nb(\n",
" size=size[col],\n",
" price=vbt.flex_select_nb(price_, i, col),\n",
" size_type=vbt.pf_enums.SizeType.TargetPercent,\n",
" direction=vbt.pf_enums.Direction.Both,\n",
" fees=vbt.flex_select_nb(fees_, i, col),\n",
" )\n",
"\n",
" price_area = vbt.pf_enums.PriceArea(\n",
" open=np.nan,\n",
" high=np.nan,\n",
" low=np.nan,\n",
" close=vbt.flex_select_nb(close, i, col),\n",
" )\n",
" val_price_now = vbt.flex_select_nb(val_price_, i, col)\n",
" exec_state = vbt.pf_enums.ExecState(\n",
" cash=cash_now,\n",
" position=last_position[col],\n",
" debt=last_debt[col],\n",
" locked_cash=last_locked_cash[col],\n",
" free_cash=free_cash_now,\n",
" val_price=val_price_now,\n",
" value=value_now,\n",
" )\n",
" _, new_exec_state = vbt.pf_nb.process_order_nb(\n",
" group=group,\n",
" col=col,\n",
" i=i,\n",
" exec_state=exec_state,\n",
" order=order,\n",
" price_area=price_area,\n",
" order_records=order_records,\n",
" order_counts=order_counts\n",
" )\n",
"\n",
" cash_now = new_exec_state.cash\n",
" free_cash_now = new_exec_state.free_cash\n",
" value_now = new_exec_state.value\n",
" last_position[col] = new_exec_state.position\n",
" last_debt[col] = new_exec_state.debt\n",
" last_locked_cash[col] = new_exec_state.locked_cash\n",
"\n",
" return vbt.nb.repartition_nb(order_records, order_counts)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e345590b-3a5a-43ec-b325-3db9766d3ea1",
"metadata": {},
"outputs": [],
"source": [
"@njit(nogil=True)\n",
"def sharpe_optimize_func_nb(\n",
" start_idx, \n",
" end_idx, \n",
" close, \n",
" num_tests, \n",
" ann_factor\n",
"):\n",
" close_period = close[start_idx:end_idx]\n",
" returns = (close_period[1:] - close_period[:-1]) / close_period[:-1]\n",
" mean = vbt.nb.nanmean_nb(returns)\n",
" cov = np.cov(returns, rowvar=False)\n",
" best_sharpe_ratio = -np.inf\n",
" weights = np.full(close.shape[1], np.nan, dtype=np.float_)\n",
" \n",
" for i in range(num_tests):\n",
" w = np.random.random_sample(close.shape[1])\n",
" w = w / np.sum(w)\n",
" p_return = np.sum(mean * w) * ann_factor\n",
" p_std = np.sqrt(np.dot(w.T, np.dot(cov, w))) * np.sqrt(ann_factor)\n",
" sharpe_ratio = p_return / p_std\n",
" if sharpe_ratio > best_sharpe_ratio:\n",
" best_sharpe_ratio = sharpe_ratio\n",
" weights = w\n",
" \n",
" return weights"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0b16d9d-688a-49b5-8738-2191b1ad966a",
"metadata": {},
"outputs": [],
"source": [
"range_starts, range_ends = data.wrapper.get_index_ranges(every=\"W\")\n",
"ann_factor = vbt.timedelta(\"365d\") / vbt.timedelta(\"1h\")\n",
"init_cash = 100\n",
"num_tests = 30\n",
"fees = 0.005\n",
"\n",
"order_records = optimize_portfolio_nb(\n",
" data.get(\"Close\").values,\n",
" data.get(\"Open\").values,\n",
" range_starts,\n",
" range_ends,\n",
" sharpe_optimize_func_nb,\n",
" optimize_args=(data.get(\"Close\").values, num_tests, ann_factor),\n",
" fees=fees,\n",
" init_cash=init_cash\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a7c2e668-d30b-4bbf-ab45-e7287eba39d8",
"metadata": {},
"outputs": [],
"source": [
"pf = vbt.Portfolio(\n",
" wrapper=symbol_wrapper.regroup(True), \n",
" close=data.get(\"Close\"), \n",
" order_records=order_records, \n",
" log_records=np.array([]), \n",
" cash_sharing=True, \n",
" init_cash=init_cash\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa378c28-5ec9-4999-88cf-edd11df53afa",
"metadata": {},
"outputs": [],
"source": [
"pf.plot_allocations().show_svg()"
]
},
{
"cell_type": "markdown",
"id": "89431545-729d-4b7f-b465-afed7168ec53",
"metadata": {},
"source": [
"### Bonus 2: Parameterization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ee3cc01-379c-4ee0-a188-78cb1fbdd526",
"metadata": {},
"outputs": [],
"source": [
"def merge_func(order_records_list, param_index):\n",
" sharpe_ratios = pd.Series(index=param_index, dtype=np.float_)\n",
" for i, order_records in enumerate(order_records_list):\n",
" pf = vbt.Portfolio(\n",
" wrapper=symbol_wrapper.regroup(True), \n",
" close=data.get(\"Close\"), \n",
" order_records=order_records, \n",
" cash_sharing=True, \n",
" init_cash=init_cash\n",
" )\n",
" sharpe_ratios.iloc[i] = pf.sharpe_ratio\n",
" return sharpe_ratios"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ffd14c31-25fd-415c-9b2f-295e31732a1d",
"metadata": {},
"outputs": [],
"source": [
"param_optimize_portfolio_nb = vbt.parameterized(\n",
" optimize_portfolio_nb, \n",
" merge_func=merge_func,\n",
" merge_kwargs=dict(param_index=vbt.Rep(\"param_index\")),\n",
" engine=\"dask\",\n",
" chunk_len=4\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1ec6941-a6df-462c-bd2d-043f1dee498b",
"metadata": {},
"outputs": [],
"source": [
"every_index = pd.Index([\"D\", \"W\", \"M\"], name=\"every\")\n",
"num_tests_index = pd.Index([30, 50, 100], name=\"num_tests\")\n",
"fees_index = pd.Index([0.0, 0.005, 0.01], name=\"fees\")\n",
"\n",
"range_starts = []\n",
"range_ends = []\n",
"for every in every_index:\n",
" index_ranges = symbol_wrapper.get_index_ranges(every=every)\n",
" range_starts.append(index_ranges[0])\n",
" range_ends.append(index_ranges[1])\n",
"num_tests = num_tests_index.tolist()\n",
"\n",
"range_starts = vbt.Param(range_starts, level=0, keys=every_index)\n",
"range_ends = vbt.Param(range_ends, level=0, keys=every_index)\n",
"num_tests = vbt.Param(num_tests, level=1, keys=num_tests_index)\n",
"fees = vbt.Param(fees_index.values, level=2, keys=fees_index)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5534b797-2c85-4c33-8143-d41b9542c1df",
"metadata": {},
"outputs": [],
"source": [
"sharpe_ratios = param_optimize_portfolio_nb(\n",
" data.get(\"Close\").values,\n",
" data.get(\"Open\").values,\n",
" range_starts,\n",
" range_ends,\n",
" sharpe_optimize_func_nb,\n",
" optimize_args=(\n",
" data.get(\"Close\").values, \n",
" num_tests, \n",
" ann_factor\n",
" ),\n",
" fees=fees,\n",
" init_cash=init_cash,\n",
" group=vbt.Rep(\"config_idx\")\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508c1fc7-c141-4428-9844-6bef69ea64cd",
"metadata": {},
"outputs": [],
"source": [
"sharpe_ratios"
]
},
{
"cell_type": "markdown",
"id": "d87c6506-c84f-49bc-9236-3804cd22dd22",
"metadata": {},
"source": [
"### Bonus 3: Hyperopt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "111953f4-6b42-4023-b204-4b4480bf1ed8",
"metadata": {},
"outputs": [],
"source": [
"def objective(kwargs):\n",
" close_values = data.get(\"Close\").values\n",
" open_values = data.get(\"Open\").values\n",
" index_ranges = symbol_wrapper.get_index_ranges(every=kwargs[\"every\"])\n",
" order_records = optimize_portfolio_nb(\n",
" close_values,\n",
" open_values,\n",
" index_ranges[0],\n",
" index_ranges[1],\n",
" sharpe_optimize_func_nb,\n",
" optimize_args=(close_values, kwargs[\"num_tests\"], ann_factor),\n",
" fees=vbt.to_2d_array(kwargs[\"fees\"]),\n",
" init_cash=init_cash\n",
" )\n",
" pf = vbt.Portfolio(\n",
" wrapper=symbol_wrapper.regroup(True), \n",
" close=data.get(\"Close\"), \n",
" order_records=order_records, \n",
" log_records=np.array([]), \n",
" cash_sharing=True, \n",
" init_cash=init_cash\n",
" )\n",
" return -pf.sharpe_ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7de2667c-977e-4595-808d-17afc7279796",
"metadata": {},
"outputs": [],
"source": [
"from hyperopt import fmin, tpe, hp\n",
"\n",
"space = {\n",
" \"every\": hp.choice(\"every\", [\"%dD\" % n for n in range(1, 100)]),\n",
" \"num_tests\": hp.quniform(\"num_tests\", 5, 100, 1),\n",
" \"fees\": hp.uniform('fees', 0, 0.05)\n",
"}\n",
"\n",
"best = fmin(\n",
" fn=objective,\n",
" space=space,\n",
" algo=tpe.suggest,\n",
" max_evals=30\n",
")\n",
"best"
]
},
{
"cell_type": "markdown",
"id": "a19b8f97-e160-4238-bafa-6701c5a7d3d7",
"metadata": {},
"source": [
"### Bonus 4: Hybrid"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d0c221b-4c46-4875-ac11-248518e025f8",
"metadata": {},
"outputs": [],
"source": [
"def optimize_func(\n",
" data, \n",
" index_slice, \n",
" temp_allocations, \n",
" temp_pfs, \n",
" threshold\n",
"):\n",
" sub_data = data.iloc[index_slice]\n",
" if len(temp_allocations) > 0:\n",
" prev_allocation = sub_data.symbol_wrapper.wrap(\n",
" [temp_allocations[-1]], \n",
" index=sub_data.wrapper.index[[0]]\n",
" )\n",
" prev_pfo = vbt.PortfolioOptimizer.from_allocations(\n",
" sub_data.symbol_wrapper,\n",
" prev_allocation\n",
" )\n",
" if len(temp_pfs) > 0:\n",
" init_cash = temp_pfs[-1].cash.iloc[-1]\n",
" init_position = temp_pfs[-1].assets.iloc[-1]\n",
" init_price = temp_pfs[-1].close.iloc[-1]\n",
" else:\n",
" init_cash = 100.\n",
" init_position = 0.\n",
" init_price = np.nan\n",
" prev_pf = prev_pfo.simulate(\n",
" sub_data,\n",
" init_cash=init_cash, \n",
" init_position=init_position,\n",
" init_price=init_price\n",
" )\n",
" temp_pfs.append(prev_pf)\n",
" should_rebalance = False\n",
" curr_alloc = prev_pf.allocations.iloc[-1].values\n",
" if (np.abs(curr_alloc - temp_allocations[-1]) >= threshold).any():\n",
" should_rebalance = True\n",
" else:\n",
" should_rebalance = True\n",
" n_symbols = len(sub_data.symbols)\n",
" if should_rebalance:\n",
" new_alloc = np.full(n_symbols, 1 / n_symbols)\n",
" else:\n",
" new_alloc = np.full(n_symbols, np.nan)\n",
" temp_allocations.append(new_alloc)\n",
" return new_alloc\n",
"\n",
"pfs = []\n",
"allocations = []\n",
"pfopt = vbt.PortfolioOptimizer.from_optimize_func(\n",
" data.symbol_wrapper,\n",
" optimize_func,\n",
" data,\n",
" vbt.Rep(\"index_slice\"),\n",
" allocations,\n",
" pfs,\n",
" 0.03,\n",
" every=\"W\"\n",
")\n",
"pf = pfopt.simulate(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1cc1c7c-e487-45a7-b36b-dbd9d6de9ee8",
"metadata": {},
"outputs": [],
"source": [
"final_values = pd.concat(map(lambda x: x.value[[-1]], pfs))\n",
"final_values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae5aeee2-7b53-4c77-b848-abe9c23b3c69",
"metadata": {},
"outputs": [],
"source": [
"pd.testing.assert_series_equal(\n",
" final_values,\n",
" pf.value.loc[final_values.index],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ed00107-34bd-4cb8-abfd-6f5432a56f2c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}