{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "GQ18Kd5F3uKe", "metadata": { "id": "GQ18Kd5F3uKe" }, "outputs": [], "source": [ "!pip install -q requests openseries" ] }, { "cell_type": "code", "execution_count": 2, "id": "1", "metadata": { "id": "1" }, "outputs": [], "source": [ "from IPython.display import HTML, display\n", "from requests import get as requests_get\n", "from os import environ as os_environ\n", "import plotly.io as pio\n", "from sys import modules as sys_modules\n", "\n", "from openseries import (\n", " OpenTimeSeries,\n", " OpenFrame,\n", " ReturnSimulation,\n", " report_html,\n", " ValueType,\n", " efficient_frontier,\n", " prepare_plot_data,\n", " sharpeplot,\n", " load_plotly_dict,\n", " get_previous_business_day_before_today,\n", ")\n", "\n", "figdict, _ = load_plotly_dict()\n", "seed = 55" ] }, { "cell_type": "code", "execution_count": 3, "id": "94d23409-1d85-47fe-bf32-772117fbb9f2", "metadata": {}, "outputs": [], "source": [ "if \"google.colab\" in sys_modules:\n", " pio.renderers.default = \"colab\"\n", "elif \"ipykernel\" in sys_modules and \"VSCODE_PID\" in os_environ:\n", " pio.renderers.default = \"vscode\"\n", "else:\n", " pio.renderers.default = \"notebook_connected\"" ] }, { "cell_type": "code", "execution_count": 4, "id": "2", "metadata": { "id": "2" }, "outputs": [], "source": [ "def make_fund_basket(positions: dict[str, float], timeout: int = 10) -> OpenFrame:\n", " response = requests_get(url=\"https://api.captor.se/public/api/nav\", timeout=timeout)\n", " response.raise_for_status()\n", "\n", " found, weights, series = set(), [], []\n", " result = response.json()\n", " for data in result:\n", " if data[\"isin\"] in positions:\n", " found.add(data[\"isin\"])\n", " weights.append(positions[data[\"isin\"]])\n", " series.append(\n", " OpenTimeSeries.from_arrays(\n", " name=data[\"longName\"],\n", " isin=data[\"isin\"],\n", " baseccy=data[\"currency\"],\n", " dates=data[\"dates\"],\n", " values=data[\"navPerUnit\"],\n", " valuetype=ValueType.PRICE,\n", " )\n", " )\n", "\n", " if len(set(positions.keys()) - found) != 0:\n", " raise ValueError(f\"Request for NAV series failed. Missing ISINs are: {set(positions.keys()) - found}\")\n", "\n", " return OpenFrame(constituents=series, weights=weights)" ] }, { "cell_type": "code", "execution_count": 5, "id": "3", "metadata": { "id": "3" }, "outputs": [], "source": [ "funds = {\n", " \"SE0015243886\": 0.2,\n", " \"SE0011337195\": 0.2,\n", " \"SE0011670843\": 0.2,\n", " \"SE0017832280\": 0.2,\n", " \"SE0017832330\": 0.2,\n", "}\n", "basket = make_fund_basket(positions=funds)\n", "basket = basket.value_nan_handle().trunc_frame().to_cumret()" ] }, { "cell_type": "code", "execution_count": 6, "id": "4", "metadata": { "id": "4" }, "outputs": [], "source": [ "portfolio = OpenTimeSeries.from_df(dframe=basket.make_portfolio(name=\"Portfolio\"))\n", "basket = basket.add_timeseries(new_series=portfolio)" ] }, { "cell_type": "code", "execution_count": 7, "id": "5", "metadata": { "id": "5" }, "outputs": [], "source": [ "figure, _ = basket.plot_series(tick_fmt=\".1%\", auto_open=False, output_type=\"div\", add_logo=False)\n", "figure = figure.update_layout(\n", " height=600,\n", " font_size=10,\n", " legend=dict(\n", " yanchor=\"bottom\",\n", " y=-0.3,\n", " xanchor=\"right\",\n", " x=0.98,\n", " orientation=\"h\"\n", " )\n", ")\n", "figure = figure.update_layout(font_size=14, width=1100, height=600)" ] }, { "cell_type": "code", "execution_count": 8, "id": "6", "metadata": { "id": "6", "outputId": "f32e4d12-d9b0-453b-bef9-712dcd98eda6" }, "outputs": [ { "data": { "text/html": [ " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "figure.show(config=figdict[\"config\"])" ] }, { "cell_type": "code", "execution_count": 9, "id": "7", "metadata": { "id": "7" }, "outputs": [], "source": [ "dataframe = basket.all_properties(properties=[\n", " \"arithmetic_ret\",\n", " \"vol\",\n", " \"ret_vol_ratio\",\n", " \"sortino_ratio\",\n", " \"worst_month\",\n", " \"cvar_down\",\n", " \"first_indices\",\n", " \"last_indices\",\n", " ]\n", ")" ] }, { "cell_type": "code", "execution_count": 10, "id": "8", "metadata": { "id": "8" }, "outputs": [], "source": [ "dataframe.columns = dataframe.columns.droplevel(level=1)\n", "\n", "formats = [\n", " \"{:.2%}\",\n", " \"{:.2%}\",\n", " \"{:.2f}\",\n", " \"{:.2f}\",\n", " \"{:.2%}\",\n", " \"{:.2%}\",\n", " \"{:%Y-%m-%d}\",\n", " \"{:%Y-%m-%d}\",\n", " ]\n", "\n", "for item, f in zip(dataframe.index, formats):\n", " dataframe.loc[item] = dataframe.loc[item].apply(\n", " lambda x, fmt=f: x if isinstance(x, str) else fmt.format(x)\n", " )" ] }, { "cell_type": "code", "execution_count": 11, "id": "9", "metadata": { "id": "9", "outputId": "1cdb5aba-c107-4697-baf6-6c0ca91a66b4", "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Captor Aster Global Credit Short-TermCaptor Aster Global High YieldCaptor Aster Global CreditCaptor Scilla Global EquityCaptor Dahlia Green BondPortfolio
Arithmetic return5.08%11.08%5.61%9.74%4.39%7.18%
Volatility1.52%6.22%7.71%9.93%2.69%3.98%
Return vol ratio3.341.780.730.981.631.80
Sortino ratio5.112.701.081.372.612.65
Worst month-0.25%-2.70%-5.49%-7.55%-2.20%-2.91%
CVaR 95.0%-0.23%-0.90%-1.04%-1.51%-0.36%-0.59%
first indices2022-12-062022-12-062022-12-062022-12-062022-12-062022-12-06
last indices2026-01-022026-01-022026-01-022026-01-022026-01-022026-01-02
\n", "
" ], "text/plain": [ " Captor Aster Global Credit Short-Term \\\n", "Arithmetic return 5.08% \n", "Volatility 1.52% \n", "Return vol ratio 3.34 \n", "Sortino ratio 5.11 \n", "Worst month -0.25% \n", "CVaR 95.0% -0.23% \n", "first indices 2022-12-06 \n", "last indices 2026-01-02 \n", "\n", " Captor Aster Global High Yield Captor Aster Global Credit \\\n", "Arithmetic return 11.08% 5.61% \n", "Volatility 6.22% 7.71% \n", "Return vol ratio 1.78 0.73 \n", "Sortino ratio 2.70 1.08 \n", "Worst month -2.70% -5.49% \n", "CVaR 95.0% -0.90% -1.04% \n", "first indices 2022-12-06 2022-12-06 \n", "last indices 2026-01-02 2026-01-02 \n", "\n", " Captor Scilla Global Equity Captor Dahlia Green Bond \\\n", "Arithmetic return 9.74% 4.39% \n", "Volatility 9.93% 2.69% \n", "Return vol ratio 0.98 1.63 \n", "Sortino ratio 1.37 2.61 \n", "Worst month -7.55% -2.20% \n", "CVaR 95.0% -1.51% -0.36% \n", "first indices 2022-12-06 2022-12-06 \n", "last indices 2026-01-02 2026-01-02 \n", "\n", " Portfolio \n", "Arithmetic return 7.18% \n", "Volatility 3.98% \n", "Return vol ratio 1.80 \n", "Sortino ratio 2.65 \n", "Worst month -2.91% \n", "CVaR 95.0% -0.59% \n", "first indices 2022-12-06 \n", "last indices 2026-01-02 " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dataframe" ] }, { "cell_type": "code", "execution_count": 12, "id": "b0da719b-7ab4-4d4f-9c5f-ded2fb0526f2", "metadata": {}, "outputs": [], "source": [ "hy = OpenTimeSeries.from_df(dframe=basket.tsdf.loc[:, (\"Captor Aster Global High Yield\", ValueType.PRICE)])\n", "eq = OpenTimeSeries.from_df(dframe=basket.tsdf.loc[:, (\"Captor Scilla Global Equity\", ValueType.PRICE)])\n", "compare = OpenFrame(constituents=[hy, eq])\n", "_, plotstring = report_html(\n", " data=compare,\n", " title=hy.label,\n", " bar_freq=\"BQE\",\n", " output_type=\"div\",\n", " add_logo=True,\n", ")" ] }, { "cell_type": "code", "execution_count": 13, "id": "213a09f8-1736-4e86-be60-edd6f32b4f32", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/mkarrin/workgit/NoteBook/venv/lib/python3.13/site-packages/IPython/core/display.py:447: UserWarning:\n", "\n", "Consider using IPython.display.IFrame instead\n", "\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "html_cleaned = plotstring.replace(\"\\n\", \" \").replace(\"\\r\", \" \")\n", "escaped_html = html_cleaned.replace('\"', \""\").replace(\"'\", \"'\")\n", "\n", "width = 1100\n", "height = 1000\n", "iframe_html = (\n", " f''\n", ")\n", "\n", "display(HTML(iframe_html))" ] }, { "cell_type": "code", "execution_count": 14, "id": "10", "metadata": { "id": "10" }, "outputs": [], "source": [ "series = ReturnSimulation.from_merton_jump_gbm(\n", " number_of_sims=4,\n", " trading_days=2512,\n", " mean_annual_return=0.05,\n", " mean_annual_vol=0.1,\n", " jumps_lamda=0.1,\n", " jumps_sigma=0.3,\n", " jumps_mu=-0.2,\n", " trading_days_in_year=252,\n", " seed=seed,\n", ")\n", "\n", "date = get_previous_business_day_before_today(markets=\"XSTO\")\n", "\n", "assets = OpenFrame(constituents=[\n", " OpenTimeSeries.from_df(dframe=series.to_dataframe(name=\"Asset\", end=date, markets=\"XSTO\"), column_nmbr=serie) for\n", " serie in range(series.number_of_sims)]).to_cumret()" ] }, { "cell_type": "code", "execution_count": 15, "id": "11", "metadata": { "id": "11" }, "outputs": [], "source": [ "simulations = 5000\n", "points = 30\n", "\n", "current = OpenTimeSeries.from_df(\n", " dframe=assets.make_portfolio(\n", " name=\"Current Portfolio\",\n", " weight_strat=\"eq_weights\",\n", " ),\n", ")\n", "frontier, simulated, optimum = efficient_frontier(\n", " eframe=assets,\n", " num_ports=simulations,\n", " seed=seed,\n", " frontier_points=points,\n", ")\n", "plotframe = prepare_plot_data(\n", " assets=assets,\n", " current=current,\n", " optimized=optimum,\n", ")\n", "simfigure, _ = sharpeplot(\n", " sim_frame=simulated,\n", " line_frame=frontier,\n", " point_frame=plotframe,\n", " point_frame_mode=\"markers+text\",\n", " title=False,\n", " add_logo=False,\n", " auto_open=False,\n", " output_type=\"div\",\n", ")\n", "simfigure = simfigure.update_layout(width=1100, height=650)" ] }, { "cell_type": "code", "execution_count": 16, "id": "12", "metadata": { "id": "12", "outputId": "9e0778c3-bf58-46e0-b450-8b93d45de93b" }, "outputs": [ { "data": { "text/html": [ "
\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "simfigure.show(config=figdict[\"config\"])" ] }, { "cell_type": "code", "execution_count": null, "id": "93e06bf4-3c39-493f-91ea-a23cad95f414", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "colab": { "provenance": [] }, "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.13.11" } }, "nbformat": 4, "nbformat_minor": 5 }