From 0b346fea5e45c31d7795cee184012bc59caefa40 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Mon, 14 Oct 2024 00:48:31 +0000 Subject: [PATCH] Work on converting old blog posts --- .../2015-11-14-welcome.ipynb | 293 ++++++++++++++++++ blog/2015-11-14-welcome/_notebook.md | 167 ++++++++++ blog/2015-11-14-welcome/index.mdx | 71 +++++ blog/2021-08-01-mdx-blog-post.mdx | 6 + blog/2021-08-01-second.mdx | 3 - docusaurus.config.ts | 18 +- package-lock.json | 270 +++++++++++++++- package.json | 4 +- src/css/custom.css | 8 +- 9 files changed, 826 insertions(+), 14 deletions(-) create mode 100644 blog/2015-11-14-welcome/2015-11-14-welcome.ipynb create mode 100644 blog/2015-11-14-welcome/_notebook.md create mode 100644 blog/2015-11-14-welcome/index.mdx delete mode 100644 blog/2021-08-01-second.mdx diff --git a/blog/2015-11-14-welcome/2015-11-14-welcome.ipynb b/blog/2015-11-14-welcome/2015-11-14-welcome.ipynb new file mode 100644 index 0000000..2fab0a3 --- /dev/null +++ b/blog/2015-11-14-welcome/2015-11-14-welcome.ipynb @@ -0,0 +1,293 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Trading Competition Optimization\n", + "\n", + "### Goal: Max return given maximum Sharpe and Drawdown" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "import Quandl\n", + "from datetime import datetime, timedelta\n", + "\n", + "tickers = ['XOM', 'CVX', 'CLB', 'OXY', 'SLB']\n", + "market_ticker = 'GOOG/NYSE_VOO'\n", + "lookback = 30\n", + "d_col = 'Close'\n", + "\n", + "data = {tick: Quandl.get('YAHOO/{}'.format(tick))[-lookback:] for tick in tickers}\n", + "market = Quandl.get(market_ticker)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Calculating the Return\n", + "We first want to know how much each ticker returned over the prior period." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'CLB': -0.0016320202164526894,\n", + " 'CVX': 0.0010319531629488911,\n", + " 'OXY': 0.00093418904454400551,\n", + " 'SLB': 0.00098431254720448159,\n", + " 'XOM': 0.00044165797556096868}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "returns = {tick: data[tick][d_col].pct_change() for tick in tickers}\n", + "\n", + "display({tick: returns[tick].mean() for tick in tickers})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Calculating the Sharpe ratio\n", + "Sharpe: ${R - R_M \\over \\sigma}$\n", + "\n", + "We use the average return over the lookback period, minus the market average return, over the ticker standard deviation to calculate the Sharpe. Shorting a stock turns a negative Sharpe positive." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'CLB': -0.10578734457846127,\n", + " 'CVX': 0.027303529817677398,\n", + " 'OXY': 0.022622210057414487,\n", + " 'SLB': 0.026950946344858676,\n", + " 'XOM': -0.0053519259698605499}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "market_returns = market.pct_change()\n", + "\n", + "sharpe = lambda ret: (ret.mean() - market_returns[d_col].mean()) / ret.std()\n", + "sharpes = {tick: sharpe(returns[tick]) for tick in tickers}\n", + "\n", + "display(sharpes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Calculating the drawdown\n", + "This one is easy - what is the maximum daily change over the lookback period? That is, because we will allow short positions, we are not concerned strictly with maximum downturn, but in general, what is the largest 1-day change?" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'CLB': 0.043551495607375035,\n", + " 'CVX': 0.044894389686214398,\n", + " 'OXY': 0.051424517867144637,\n", + " 'SLB': 0.034774627850375328,\n", + " 'XOM': 0.035851524605672758}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "drawdown = lambda ret: ret.abs().max()\n", + "drawdowns = {tick: drawdown(returns[tick]) for tick in tickers}\n", + "\n", + "display(drawdowns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Performing the optimization\n", + "\n", + "$\\begin{align}\n", + "max\\ \\ & \\mu \\cdot \\omega\\\\\n", + "s.t.\\ \\ & \\vec{1} \\omega = 1\\\\\n", + "& \\vec{S} \\omega \\ge s\\\\\n", + "& \\vec{D} \\cdot | \\omega | \\le d\\\\\n", + "& \\left|\\omega\\right| \\le l\\\\\n", + "\\end{align}$\n", + "\n", + "We want to maximize average return subject to having a full portfolio, Sharpe above a specific level, drawdown below a level, and leverage not too high - that is, don't have huge long/short positions." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Optimization terminated successfully.'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Holdings: [('XOM', 5.8337945679814904), ('CVX', 42.935064321851307), ('CLB', -124.5), ('OXY', 36.790387773552119), ('SLB', 39.940753336615096)]\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Expected Return: 32.375%'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'Expected Max Drawdown: 4.34%'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "from scipy.optimize import minimize\n", + "\n", + "#sharpe_limit = .1\n", + "drawdown_limit = .05\n", + "leverage = 250\n", + "\n", + "# Use the map so we can guarantee we maintain the correct order\n", + "# sharpe_a = np.array(list(map(lambda tick: sharpes[tick], tickers))) * -1 # So we can write as upper-bound\n", + "dd_a = np.array(list(map(lambda tick: drawdowns[tick], tickers)))\n", + "returns_a = np.array(list(map(lambda tick: returns[tick].mean(), tickers))) # Because minimizing\n", + "\n", + "meets_sharpe = lambda x: sum(abs(x) * sharpe_a) - sharpe_limit\n", + "def meets_dd(x):\n", + " portfolio = sum(abs(x))\n", + " if portfolio < .1:\n", + " # If there are no stocks in the portfolio,\n", + " # we can accidentally induce division by 0,\n", + " # or division by something small enough to cause infinity\n", + " return 0\n", + " \n", + " return drawdown_limit - sum(abs(x) * dd_a) / sum(abs(x))\n", + "\n", + "is_portfolio = lambda x: sum(x) - 1\n", + "\n", + "def within_leverage(x):\n", + " return leverage - sum(abs(x))\n", + "\n", + "objective = lambda x: sum(x * returns_a) * -1 # Because we're minimizing\n", + "bounds = ((None, None),) * len(tickers)\n", + "x = np.zeros(len(tickers))\n", + "\n", + "constraints = [\n", + " {\n", + " 'type': 'eq',\n", + " 'fun': is_portfolio\n", + " }, {\n", + " 'type': 'ineq',\n", + " 'fun': within_leverage\n", + " #}, {\n", + " # 'type': 'ineq',\n", + " # 'fun': meets_sharpe\n", + " }, {\n", + " 'type': 'ineq',\n", + " 'fun': meets_dd\n", + " }\n", + "]\n", + "\n", + "optimal = minimize(objective, x, bounds=bounds, constraints=constraints,\n", + " options={'maxiter': 500})\n", + "\n", + "# Optimization time!\n", + "display(optimal.message)\n", + "\n", + "display(\"Holdings: {}\".format(list(zip(tickers, optimal.x))))\n", + "\n", + "expected_return = optimal.fun * -100 # multiply by -100 to scale, and compensate for minimizing\n", + "display(\"Expected Return: {:.3f}%\".format(expected_return))\n", + "\n", + "expected_drawdown = sum(abs(optimal.x) * dd_a) / sum(abs(optimal.x)) * 100\n", + "display(\"Expected Max Drawdown: {0:.2f}%\".format(expected_drawdown))\n", + "\n", + "# TODO: Calculate expected Sharpe" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.5.0" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/blog/2015-11-14-welcome/_notebook.md b/blog/2015-11-14-welcome/_notebook.md new file mode 100644 index 0000000..aa875ed --- /dev/null +++ b/blog/2015-11-14-welcome/_notebook.md @@ -0,0 +1,167 @@ +**Goal: Max return given maximum Sharpe and Drawdown** + + +```python +from IPython.display import display +import Quandl +from datetime import datetime, timedelta + +tickers = ['XOM', 'CVX', 'CLB', 'OXY', 'SLB'] +market_ticker = 'GOOG/NYSE_VOO' +lookback = 30 +d_col = 'Close' + +data = {tick: Quandl.get('YAHOO/{}'.format(tick))[-lookback:] for tick in tickers} +market = Quandl.get(market_ticker) +``` + +## Calculating the Return +We first want to know how much each ticker returned over the prior period. + + +```python +returns = {tick: data[tick][d_col].pct_change() for tick in tickers} + +display({tick: returns[tick].mean() for tick in tickers}) +``` + +``` + {'CLB': -0.0016320202164526894, + 'CVX': 0.0010319531629488911, + 'OXY': 0.00093418904454400551, + 'SLB': 0.00098431254720448159, + 'XOM': 0.00044165797556096868} +``` + +## Calculating the Sharpe ratio +Sharpe: $R - R_M \over \sigma$ + +We use the average return over the lookback period, minus the market average return, over the ticker standard deviation to calculate the Sharpe. Shorting a stock turns a negative Sharpe positive. + + +```python +market_returns = market.pct_change() + +sharpe = lambda ret: (ret.mean() - market_returns[d_col].mean()) / ret.std() +sharpes = {tick: sharpe(returns[tick]) for tick in tickers} + +display(sharpes) +``` + +``` + {'CLB': -0.10578734457846127, + 'CVX': 0.027303529817677398, + 'OXY': 0.022622210057414487, + 'SLB': 0.026950946344858676, + 'XOM': -0.0053519259698605499} +``` + +## Calculating the drawdown +This one is easy - what is the maximum daily change over the lookback period? That is, because we will allow short positions, we are not concerned strictly with maximum downturn, but in general, what is the largest 1-day change? + + +```python +drawdown = lambda ret: ret.abs().max() +drawdowns = {tick: drawdown(returns[tick]) for tick in tickers} + +display(drawdowns) +``` + +``` + {'CLB': 0.043551495607375035, + 'CVX': 0.044894389686214398, + 'OXY': 0.051424517867144637, + 'SLB': 0.034774627850375328, + 'XOM': 0.035851524605672758} +``` + +## Performing the optimization + +$$ +\begin{align*} + max\ \ & \mu \cdot \omega \\ + s.t.\ \ & \vec{1} \omega = 1\\ + & \vec{S} \omega \ge s\\ + & \vec{D} \cdot | \omega | \le d\\ + & \left|\omega\right| \le l\\ +\end{align*} +$$ + +We want to maximize average return subject to having a full portfolio, Sharpe above a specific level, drawdown below a level, and leverage not too high - that is, don't have huge long/short positions. + + +```python +import numpy as np +from scipy.optimize import minimize + +#sharpe_limit = .1 +drawdown_limit = .05 +leverage = 250 + +# Use the map so we can guarantee we maintain the correct order +# sharpe_a = np.array(list(map(lambda tick: sharpes[tick], tickers))) * -1 # So we can write as upper-bound +dd_a = np.array(list(map(lambda tick: drawdowns[tick], tickers))) +returns_a = np.array(list(map(lambda tick: returns[tick].mean(), tickers))) # Because minimizing + +meets_sharpe = lambda x: sum(abs(x) * sharpe_a) - sharpe_limit +def meets_dd(x): + portfolio = sum(abs(x)) + if portfolio < .1: + # If there are no stocks in the portfolio, + # we can accidentally induce division by 0, + # or division by something small enough to cause infinity + return 0 + + return drawdown_limit - sum(abs(x) * dd_a) / sum(abs(x)) + +is_portfolio = lambda x: sum(x) - 1 + +def within_leverage(x): + return leverage - sum(abs(x)) + +objective = lambda x: sum(x * returns_a) * -1 # Because we're minimizing +bounds = ((None, None),) * len(tickers) +x = np.zeros(len(tickers)) + +constraints = [ + { + 'type': 'eq', + 'fun': is_portfolio + }, { + 'type': 'ineq', + 'fun': within_leverage + #}, { + # 'type': 'ineq', + # 'fun': meets_sharpe + }, { + 'type': 'ineq', + 'fun': meets_dd + } +] + +optimal = minimize(objective, x, bounds=bounds, constraints=constraints, + options={'maxiter': 500}) + +# Optimization time! +display(optimal.message) + +display("Holdings: {}".format(list(zip(tickers, optimal.x)))) + +expected_return = optimal.fun * -100 # multiply by -100 to scale, and compensate for minimizing +display("Expected Return: {:.3f}%".format(expected_return)) + +expected_drawdown = sum(abs(optimal.x) * dd_a) / sum(abs(optimal.x)) * 100 +display("Expected Max Drawdown: {0:.2f}%".format(expected_drawdown)) + +# TODO: Calculate expected Sharpe +``` + +``` + 'Optimization terminated successfully.' + + "Holdings: [('XOM', 5.8337945679814904), ('CVX', 42.935064321851307), ('CLB', -124.5), ('OXY', 36.790387773552119), ('SLB', 39.940753336615096)]" + + 'Expected Return: 32.375%' + + 'Expected Max Drawdown: 4.34%' +``` diff --git a/blog/2015-11-14-welcome/index.mdx b/blog/2015-11-14-welcome/index.mdx new file mode 100644 index 0000000..32e67e8 --- /dev/null +++ b/blog/2015-11-14-welcome/index.mdx @@ -0,0 +1,71 @@ +--- +title: Welcome, and an algorithm +date: 2015-11-19 +last_update: + date: 2015-12-05 +slug: 2015/11/welcome +authors: [bspeice] +tags: [trading] +--- + +import Notebook from './_notebook.md' + +Hello! Glad to meet you. I'm currently a student at Columbia University +studying Financial Engineering, and want to give an overview of the projects +I'm working on! + + + +To start things off, Columbia has been hosting a trading competition that +myself and another partner are competing in. I'm including a notebook of the +algorithm that we're using, just to give a simple overview of a miniature +algorithm. + +The competition is scored in 3 areas: + +- Total return +- [Sharpe ratio](https://en.wikipedia.org/wiki/Sharpe_ratio) +- Maximum drawdown + +Our algorithm uses a basic momentum strategy: in the given list of potential +portfolios, pick the stocks that have been performing well in the past 30 +days. Then, optimize for return subject to the drawdown being below a specific +level. We didn't include the Sharpe ratio as a constraint, mostly because +we were a bit late entering the competition. + +I'll be updating this post with the results of our algorithm as they come along! + +--- + +**UPDATE 12/5/2015**: Now that the competition has ended, I wanted to update +how the algorithm performed. Unfortunately, it didn't do very well. I'm planning +to make some tweaks over the coming weeks, and do another forward test in January. + +- After week 1: Down .1% +- After week 2: Down 1.4% +- After week 3: Flat + +And some statistics for all teams participating in the competition: + + + + + + + + + + + + + + + + + + +
Max Return74.1%
Min Return-97.4%
Average Return-.1%
Std Dev of Returns19.6%
+ +--- + + \ No newline at end of file diff --git a/blog/2021-08-01-mdx-blog-post.mdx b/blog/2021-08-01-mdx-blog-post.mdx index 52012eb..db4736c 100644 --- a/blog/2021-08-01-mdx-blog-post.mdx +++ b/blog/2021-08-01-mdx-blog-post.mdx @@ -12,6 +12,12 @@ Hello? Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). +
+ Hello + + Testing - {1 + 2} +
+ :::tip diff --git a/blog/2021-08-01-second.mdx b/blog/2021-08-01-second.mdx deleted file mode 100644 index 00928b7..0000000 --- a/blog/2021-08-01-second.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Another title ---- \ No newline at end of file diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 453b37d..c1e296a 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -1,6 +1,8 @@ import {themes as prismThemes} from 'prism-react-renderer'; import type {Config} from '@docusaurus/types'; import type * as Preset from '@docusaurus/preset-classic'; +import remarkMath from 'remark-math'; +import rehypeKatex from 'rehype-katex'; const config: Config = { title: 'The Old Speice Guy', @@ -34,6 +36,7 @@ const config: Config = { blog: { routeBasePath: "/", showReadingTime: true, + showLastUpdateTime: true, feedOptions: { type: ['rss', 'atom'], xslt: true, @@ -42,10 +45,12 @@ const config: Config = { onInlineTags: 'warn', onInlineAuthors: 'warn', onUntruncatedBlogPosts: 'warn', + remarkPlugins: [remarkMath], + rehypePlugins: [rehypeKatex] }, theme: { customCss: ['./src/css/custom.css'] - } + }, } satisfies Preset.Options, ], ], @@ -75,7 +80,16 @@ const config: Config = { darkTheme: prismThemes.oneDark, }, } satisfies Preset.ThemeConfig, - plugins: [require.resolve('docusaurus-lunr-search')] + plugins: [require.resolve('docusaurus-lunr-search')], + stylesheets: [ + { + href: 'https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css', + type: 'text/css', + integrity: + 'sha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM', + crossorigin: 'anonymous', + }, + ] }; export default config; diff --git a/package-lock.json b/package-lock.json index a0731f8..8b1f922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,9 @@ "docusaurus-lunr-search": "^3.5.0", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.5.2", @@ -3762,6 +3764,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -7837,6 +7845,55 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz", + "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^8.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", @@ -9190,6 +9247,31 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9678,6 +9760,25 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", @@ -10476,6 +10577,81 @@ ], "license": "MIT" }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", @@ -13773,6 +13949,68 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-parse": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-7.0.1.tgz", @@ -14039,6 +14277,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-mdx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", @@ -15721,6 +15975,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", diff --git a/package.json b/package.json index ba7bb8e..16e7175 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "docusaurus-lunr-search": "^3.5.0", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.5.2", diff --git a/src/css/custom.css b/src/css/custom.css index 93bcd1b..2deadc8 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,12 +1,6 @@ -.container { - margin: 0 1rem; -} -.footer__col { - margin-bottom: calc(var(--ifm-spacing-vertical)) -} :root { --ifm-container-width: 1280px; - --ifm-footer-padding-vertical: 1rem; + --ifm-footer-padding-vertical: .5rem; } .header-github-link:hover {