{ "cells": [ { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.3.0'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'plotly': 'https://cdn.plot.ly/plotly-2.18.0.min', 'tabulator': 'https://cdn.jsdelivr.net/npm/tabulator-tables@5.5.0/dist/js/tabulator', 'moment': 'https://cdn.jsdelivr.net/npm/luxon/build/global/luxon.min', 'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"plotly\"], function(Plotly) {\n\twindow.Plotly = Plotly\n\ton_load()\n })\n require([\"tabulator\"], function(Tabulator) {\n\twindow.Tabulator = Tabulator\n\ton_load()\n })\n require([\"moment\"], function(moment) {\n\twindow.moment = moment\n\ton_load()\n })\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 12;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['Plotly'] !== undefined) && (!(window['Plotly'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.1/dist/bundled/plotlyplot/plotly-2.18.0.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Tabulator'] !== undefined) && (!(window['Tabulator'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.jsdelivr.net/npm/tabulator-tables@5.5.0/dist/js/tabulator.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['moment'] !== undefined) && (!(window['moment'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.1/dist/bundled/datatabulator/luxon/build/global/luxon.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.3.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.holoviz.org/panel/1.3.1/dist/bundled/jquery/jquery.slim.min.js\", \"https://cdn.holoviz.org/panel/1.3.1/dist/bundled/plotlyplot/plotly-2.18.0.min.js\", \"https://cdn.jsdelivr.net/npm/tabulator-tables@5.5.0/dist/js/tabulator.js\", \"https://cdn.holoviz.org/panel/1.3.1/dist/bundled/datatabulator/luxon/build/global/luxon.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.3.0.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.0.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.0.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.0.min.js\", \"https://cdn.jsdelivr.net/npm/@holoviz/panel@1.3.6/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [\"https://cdn.holoviz.org/panel/1.3.1/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/css/tabulator_simple.min.css\"];\n var inline_js = [ function(Bokeh) {\n inject_raw_css(\".tabulator{position:relative;border:1px solid #999;font-size:14px;text-align:left;overflow:hidden;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select{user-select:none}.tabulator .tabulator-header{position:relative;box-sizing:border-box;width:100%;border-bottom:1px solid #999;background-color:#fff;color:#555;font-weight:700;white-space:nowrap;overflow:hidden;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{position:relative;overflow:hidden}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{display:inline-flex;position:relative;box-sizing:border-box;flex-direction:column;justify-content:flex-start;border-right:1px solid #ddd;background:#fff;text-align:left;vertical-align:bottom;overflow:hidden}.tabulator .tabulator-header .tabulator-col.tabulator-moving{position:absolute;border:1px solid #999;background:#e6e6e6;pointer-events:none}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;position:relative;padding:4px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{white-space:normal;text-overflow:clip}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{box-sizing:border-box;width:100%;border:1px solid #999;padding:1px;background:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{display:flex;align-items:center;position:absolute;top:0;bottom:0;right:4px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #bbb}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{position:relative;display:flex;border-top:1px solid #ddd;overflow:hidden;margin-right:-1px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{position:relative;box-sizing:border-box;margin-top:2px;width:100%;text-align:center}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{width:0;height:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{cursor:pointer;background-color:#e6e6e6}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-bottom:6px solid #555}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-top:none;border-bottom:6px solid #bbb}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-bottom:6px solid #555}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-top:none;border-bottom:6px solid #666}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{cursor:pointer;border-top:6px solid #555}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{writing-mode:vertical-rl;text-orientation:mixed;display:flex;align-items:center;justify-content:center}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-right:0;padding-bottom:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{justify-content:center;left:0;right:0;top:4px;bottom:auto}.tabulator .tabulator-header .tabulator-frozen{position:sticky;left:0;z-index:10}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #ddd}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #ddd}.tabulator .tabulator-header .tabulator-calcs-holder{box-sizing:border-box;background:#fff!important;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#fff!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle,.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{position:relative;width:100%;white-space:nowrap;overflow:auto;-webkit-overflow-scrolling:touch}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{box-sizing:border-box;display:flex;align-items:center;justify-content:center;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%;min-width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{display:inline-block;text-align:center;padding:10px;color:#ccc;font-weight:700;font-size:20px;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{position:relative;display:inline-block;background-color:#fff;white-space:nowrap;overflow:visible;color:#333}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{font-weight:700;background:#f2f2f2!important}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #ddd}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #ddd}.tabulator .tabulator-footer{border-top:1px solid #999;background-color:#fff;color:#555;font-weight:700;white-space:nowrap;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator .tabulator-footer .tabulator-footer-contents{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder{box-sizing:border-box;width:100%;text-align:left;background:#fff!important;border-bottom:1px solid #ddd;border-top:1px solid #ddd;overflow:hidden}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{display:inline-block;background:#fff!important}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{margin-bottom:-5px;border-bottom:none}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{flex:1;text-align:right;color:#555;font-family:inherit;font-weight:inherit;font-size:inherit}.tabulator .tabulator-footer .tabulator-page-size{display:inline-block;margin:0 5px;padding:2px 5px;border:1px solid #aaa;border-radius:3px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{display:inline-block;margin:0 2px;padding:2px 5px;border:1px solid #aaa;border-radius:3px;background:hsla(0,0%,100%,.2)}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(.disabled):hover{cursor:pointer;background:rgba(0,0,0,.2);color:#fff}}.tabulator .tabulator-col-resize-handle{position:relative;display:inline-block;width:6px;margin-left:-3px;margin-right:-3px;z-index:10;vertical-align:middle}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{width:3px;margin-right:0}.tabulator .tabulator-alert{position:absolute;display:flex;align-items:center;top:0;left:0;z-index:100;height:100%;width:100%;background:rgba(0,0,0,.4);text-align:center}.tabulator .tabulator-alert .tabulator-alert-msg{display:inline-block;margin:0 auto;padding:10px 20px;border-radius:10px;background:#fff;font-weight:700;font-size:16px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{position:relative;box-sizing:border-box;min-height:22px}.tabulator-row,.tabulator-row.tabulator-row-even{background-color:#fff}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{border:1px solid #000;background:#fff}.tabulator-row.tabulator-moving{position:absolute;border-top:1px solid #ddd;border-bottom:1px solid #ddd;pointer-events:none;z-index:15}.tabulator-row .tabulator-row-resize-handle{position:absolute;right:0;bottom:0;left:0;height:5px}.tabulator-row .tabulator-row-resize-handle.prev{top:0;bottom:auto}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{box-sizing:border-box;padding:5px;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{display:inline-block;position:relative;box-sizing:border-box;padding:4px;border-right:1px solid #ddd;vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tabulator-row .tabulator-cell.tabulator-frozen{display:inline-block;position:sticky;left:0;background-color:inherit;z-index:10}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #ddd}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #ddd}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{border:1px;background:transparent;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{border:1px;background:transparent;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{display:inline-flex;align-items:center;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{width:100%;height:3px;margin-top:2px;background:#666}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{display:inline-block;vertical-align:middle;height:9px;width:7px;margin-top:-9px;margin-right:5px;border-bottom-left-radius:1px;border-left:2px solid #ddd;border-bottom:2px solid #ddd}.tabulator-row .tabulator-cell .tabulator-data-tree-control{display:inline-flex;justify-content:center;align-items:center;vertical-align:middle;height:11px;width:11px;margin-right:5px;border:1px solid #333;border-radius:2px;background:rgba(0,0,0,.1);overflow:hidden}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{cursor:pointer;background:rgba(0,0,0,.2)}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{display:inline-block;position:relative;height:7px;width:1px;background:transparent}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{position:absolute;content:\\\"\\\";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{display:inline-block;position:relative;height:7px;width:1px;background:#333}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{position:absolute;content:\\\"\\\";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{display:inline-flex;align-items:center;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;height:15px;width:15px;border-radius:20px;background:#666;color:#fff;font-weight:700;font-size:1.1em}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{opacity:.7;cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{display:inline-block;height:14px;width:14px;border-radius:14px}.tabulator-row.tabulator-group{box-sizing:border-box;border-bottom:1px solid #999;border-right:1px solid #ddd;border-top:1px solid #999;padding:5px 5px 5px 10px;background:#ccc;font-weight:700;min-width:100%}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{cursor:pointer;background-color:rgba(0,0,0,.1)}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{margin-right:10px;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;border-bottom:0}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{display:inline-block;width:0;height:0;margin-right:16px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:0;border-left:6px solid #666;vertical-align:middle}.tabulator-row.tabulator-group span{margin-left:10px;color:#d00}.tabulator-popup-container{position:absolute;display:inline-block;box-sizing:border-box;background:#fff;border:1px solid #ddd;box-shadow:0 0 5px 0 rgba(0,0,0,.2);font-size:14px;overflow-y:auto;-webkit-overflow-scrolling:touch;z-index:10000}.tabulator-popup{padding:5px;border-radius:3px}.tabulator-tooltip{max-width:Min(500px,100%);padding:3px 5px;border-radius:2px;box-shadow:none;font-size:12px;pointer-events:none}.tabulator-menu .tabulator-menu-item{position:relative;box-sizing:border-box;padding:5px 10px;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{cursor:pointer;background:#fff}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{display:inline-block;position:absolute;top:calc(5px + .4em);right:10px;height:7px;width:7px;content:\\\"\\\";border-color:#ddd;border-style:solid;border-width:1px 1px 0 0;vertical-align:top;transform:rotate(45deg)}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #ddd}.tabulator-edit-list{max-height:200px;font-size:14px;overflow-y:auto;-webkit-overflow-scrolling:touch}.tabulator-edit-list .tabulator-edit-list-item{padding:4px;color:#333;outline:none}.tabulator-edit-list .tabulator-edit-list-item.active{color:#fff;background:#1d68cd}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{cursor:pointer;color:#fff;background:#1d68cd}}.tabulator-edit-list .tabulator-edit-list-placeholder{padding:4px;color:#333;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #ddd;padding:6px 4px 4px;color:#333;font-weight:700}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{text-align:initial;direction:rtl}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{text-align:initial;border-left:1px solid #ddd;border-right:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-right:0;margin-left:-1px}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:0;padding-left:25px}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-right:initial;border-left:1px solid #ddd}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{margin-right:0;margin-left:5px;border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #ddd}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-right:0;margin-left:5px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #ddd}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #ddd}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{width:3px;margin-left:0;margin-right:-3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{position:absolute;top:0;bottom:0;left:0;right:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{display:inline-block;vertical-align:middle;height:9px;width:7px;margin-top:-9px;margin-right:5px;border-bottom-left-radius:1px;border-left:2px solid #ddd;border-bottom:2px solid #ddd}.tabulator-print-table .tabulator-print-table-group{box-sizing:border-box;border-bottom:1px solid #999;border-right:1px solid #ddd;border-top:1px solid #999;padding:5px 5px 5px 10px;background:#ccc;font-weight:700;min-width:100%}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{cursor:pointer;background-color:rgba(0,0,0,.1)}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{margin-right:10px;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;border-bottom:0}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{display:inline-block;width:0;height:0;margin-right:16px;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:0;border-left:6px solid #666;vertical-align:middle}.tabulator-print-table .tabulator-print-table-group span{color:#d00}.tabulator-print-table .tabulator-data-tree-control{display:inline-flex;justify-content:center;align-items:center;vertical-align:middle;height:11px;width:11px;margin-right:5px;border:1px solid #333;border-radius:2px;background:rgba(0,0,0,.1);overflow:hidden}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{cursor:pointer;background:rgba(0,0,0,.2)}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{display:inline-block;position:relative;height:7px;width:1px;background:transparent}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{position:absolute;content:\\\"\\\";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{display:inline-block;position:relative;height:7px;width:1px;background:#333}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{position:absolute;content:\\\"\\\";left:-3px;top:3px;height:1px;width:7px;background:#333}.tabulator{border:none;background-color:#fff}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f2f2f2!important;border-bottom:1px solid #999}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f2f2f2!important}.tabulator .tabulator-tableholder .tabulator-placeholder span{color:#000}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f2f2f2!important;border-bottom:1px solid #fff}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f2f2f2!important}.tabulator-row{border-bottom:1px solid #ddd}.tabulator-row .tabulator-cell:last-of-type{border-right:none}.tabulator-row.tabulator-group span{color:#666}.tabulator-print-table .tabulator-print-table-group span{margin-left:10px;color:#666}\\n/*# sourceMappingURL=tabulator_simple.min.css.map */\");\n }, function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));", "application/vnd.holoviews_load.v0+json": "" }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n", "application/vnd.holoviews_load.v0+json": "" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ] }, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "8c44109f-f218-4d03-924b-036e03d53c46" } }, "output_type": "display_data" } ], "source": [ "import panel as pn\n", "import pandas as pd\n", "import yfinance as yf\n", "from datetime import datetime\n", "import plotly.graph_objects as go\n", "from plotly.offline import init_notebook_mode\n", "import requests\n", "import copy\n", "import cufflinks as cf\n", "import warnings\n", "from bokeh.models.widgets.tables import BooleanFormatter\n", "import matplotlib.pyplot as plt\n", "from matplotlib.figure import Figure\n", "import seaborn as sns\n", "import numpy as np\n", "import scipy.stats as scs\n", "import statsmodels.api as sm\n", "import statsmodels.tsa.api as smt\n", "\n", "\n", "plt.style.use(\"dark_background\")\n", "\n", "warnings.filterwarnings(\"ignore\")\n", "\n", "cf.go_offline()\n", "init_notebook_mode()\n", "\n", "pn.extension(\"plotly\", \"tabulator\", \"ipywidgets\", sizing_mode=\"stretch_width\")\n", "\n", "TEMPLATE = \"plotly_dark\"\n", "TICKER_DEFAULT = \"MSFT\"\n", "# set the default start date to 1 year ago\n", "START_DATE_DEFAULT = (datetime.now() - pd.DateOffset(years=1)).strftime(\"%Y-%m-%d\")" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "stocks = sorted(\n", " [\n", " \"MSFT\", # Microsoft\n", " \"GOOGL\", # Google\n", " \"META\", # Meta\n", " \"TSLA\", # Tesla\n", " \"AAPL\", # Apple\n", " \"AMZN\", # Amazon\n", " \"NFLX\", # Netflix\n", " \"NVDA\", # Nvidia\n", " \"AMD\", # AMD\n", " \"INTC\", # Intel\n", " \"CSCO\", # Cisco\n", " \"QCOM\", # Qualcomm\n", " \"ADBE\", # Adobe\n", " \"PYPL\", # PayPal\n", " \"CRM\", # Salesforce\n", " \"ORCL\", # Oracle\n", " \"IBM\", # IBM\n", " \"TXN\", # Texas Instruments\n", " \"MU\", # Micron\n", " \"AVGO\", # Broadcom\n", " \"INTU\", # Intuit\n", " \"NOW\", # ServiceNow\n", " \"UBER\", # Uber\n", " \"SNAP\", # Snap\n", " \"LYFT\", # Lyft\n", " \"ZM\", # Zoom\n", " \"WORK\", # Slack\n", " \"PINS\", # Pinterest\n", " \"SPOT\", # Spotify\n", " \"SQ\", # Square\n", " \"SHOP\", # Shopify\n", " \"ROKU\", # Roku\n", " \"DOCU\", # DocuSign\n", " \"OKTA\", # Okta\n", " \"ZS\", # Zscaler\n", " \"CRWD\", # CrowdStrike\n", " \"MDB\", # MongoDB\n", " \"DDOG\", # Datadog\n", " \"NET\", # Cloudflare\n", " \"ESTC\", # Elastic\n", " \"PANW\", # Palo Alto Networks\n", " \"VMW\", # VMware\n", " \"SPLK\", # Splunk\n", " \"WDAY\", # Workday\n", " \"TEAM\", # Atlassian\n", " \"COUP\", # Coupa\n", " \"AYX\", # Alteryx\n", " \"ZEN\", # Zendesk\n", " \"HUBS\", # Hubspot\n", " \"DT\", # Dynatrace\n", " \"ZI\", # ZoomInfo\n", " \"FSLY\", # Fastly\n", " \"TWLO\", # Twilio\n", " ]\n", ")" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "@pn.cache(ttl=600)\n", "def get_historical_data(\n", " ticker=TICKER_DEFAULT,\n", " start_date=START_DATE_DEFAULT,\n", " end_date=datetime.now().strftime(\"%Y-%m-%d\"),\n", "):\n", " \"\"\"\n", " Downloads the historical data from Yahoo Finance and caches it.\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " start_date : str\n", " The start date for the historical data\n", " end_date : str\n", " The end date for the historical data\n", " \"\"\"\n", "\n", " df = yf.download(\n", " ticker, start=start_date, end=end_date, progress=False, auto_adjust=True\n", " )\n", " return df\n", "\n", "\n", "def get_candlestick_chart(\n", " ticker=TICKER_DEFAULT,\n", " start_date=START_DATE_DEFAULT,\n", " end_date=datetime.now().strftime(\"%Y-%m-%d\"),\n", "):\n", " \"\"\"\n", " Returns a candlestick chart\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " start_date : str\n", " The start date for the historical data\n", " end_date : str\n", " The end date for the historical data\n", " \"\"\"\n", " df = get_historical_data(ticker, start_date, end_date)\n", " fig = go.Figure(\n", " data=[\n", " go.Candlestick(\n", " x=df.index,\n", " open=df[\"Open\"],\n", " high=df[\"High\"],\n", " low=df[\"Low\"],\n", " close=df[\"Close\"],\n", " name=\"Candlestick\",\n", " )\n", " ]\n", " )\n", " fig.update_layout(\n", " template=TEMPLATE,\n", " title=\"Candlestick Chart\",\n", " xaxis_title=\"Date\",\n", " yaxis_title=\"Price\",\n", " )\n", " return fig\n", "\n", "\n", "def get_line_chart(\n", " ticker=\"MSFT\", start_date=\"2020-01-01\", end_date=datetime.now().strftime(\"%Y-%m-%d\")\n", "):\n", " \"\"\"\n", " Returns a line chart\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " start_date : str\n", " The start date for the historical data\n", " end_date : str\n", " The end date for the historical data\n", " \"\"\"\n", " df = get_historical_data(ticker, start_date, end_date)\n", " fig = go.Figure(\n", " data=[\n", " go.Scatter(\n", " x=df.index,\n", " y=df[\"Close\"],\n", " name=\"Close\",\n", " )\n", " ]\n", " )\n", " fig.update_layout(\n", " title=\"Line Chart\", xaxis_title=\"Date\", yaxis_title=\"Price\", template=TEMPLATE\n", " )\n", " return fig\n", "\n", "\n", "def download_company_logo(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Download a company's logo based on its ticker symbol.\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " # URL format for logo images from a stock market website\n", " url = f\"https://storage.googleapis.com/iex/api/logos/{ticker}.png\"\n", "\n", " try:\n", " response = requests.get(url)\n", " # Check if the response is successful (status code 200)\n", " if response.status_code == 200:\n", " return response.content\n", " else:\n", " return f\"Failed to download logo. Status code: {response.status_code}\"\n", " except Exception as e:\n", " return str(e)\n", "\n", "\n", "def get_close_price_info(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create a Panel with the company logo and the last close price.\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " # get the last close price and last close date\n", " last_close_price, last_close_date = get_ticker_last_close_price(ticker)\n", "\n", " # Download the logo for Microsoft (MSFT)\n", " logo_content = download_company_logo(ticker)\n", "\n", " # Create a Panel image from the logo bytes\n", " logo = pn.panel(logo_content, width=100, height=100, align=\"center\")\n", "\n", " # Create a Panel indicator for the close price\n", "\n", " close_price = pn.indicators.Number(\n", " name=f\"Close Price (USD, {last_close_date.strftime('%Y-%m-%d')})\",\n", " value=299,\n", " format=f\"$ {last_close_price:.2f}\",\n", " colors=[(33, \"green\"), (66, \"gold\"), (100, \"red\")],\n", " )\n", " return pn.Row(logo, close_price)\n", "\n", "\n", "def get_ticker_last_close_price(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Get the last close price and last close date for a ticker.\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " df = get_historical_data(ticker)\n", " last_close_price = df[\"Close\"].iloc[-1]\n", " last_close_date = df.index[-1]\n", " return last_close_price, last_close_date\n", "\n", "\n", "@pn.cache()\n", "def get_ticker_info(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker info\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_info = ticker.info\n", "\n", " return ticker_info\n", "\n", "\n", "def get_business_summary(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with business summary\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " business_summary = ticker.info[\"longBusinessSummary\"]\n", "\n", " return pn.pane.Markdown(f\"## Business Summary\\n{business_summary}\")\n", "\n", "\n", "def get_company_officers(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with company officers\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " info = copy.deepcopy(get_ticker_info(ticker))\n", "\n", " company_officers = info[\"companyOfficers\"]\n", "\n", " # format yearBorn values to be integers\n", " for officer in company_officers:\n", " if \"yearBorn\" in officer.keys():\n", " officer[\"yearBorn\"] = f\"{int(officer['yearBorn']):.0f}\"\n", " if \"fiscalYear\" in officer.keys():\n", " officer[\"fiscalYear\"] = f\"{int(officer['fiscalYear']):.0f}\"\n", " if \"totalPay\" in officer.keys():\n", " officer[\"totalPay\"] = f\"{float(officer['totalPay']):.2f}\"\n", " if \"age\" in officer.keys():\n", " officer[\"age\"] = f\"{float(officer['age']):.0f}\"\n", "\n", " # remove unneeded keys\n", " for officer in company_officers:\n", " del officer[\"maxAge\"]\n", " del officer[\"exercisedValue\"]\n", " del officer[\"unexercisedValue\"]\n", "\n", " company_officers = pd.DataFrame(company_officers)\n", " company_officers = company_officers.reset_index(drop=True)\n", "\n", " # reorder columns\n", " company_officers = company_officers[\n", " [\n", " \"name\",\n", " \"title\",\n", " \"yearBorn\",\n", " \"age\",\n", " \"totalPay\",\n", " \"fiscalYear\",\n", " ]\n", " ]\n", "\n", " company_officers = (\n", " company_officers.style.set_properties(\n", " **{\"text-align\": \"left\"}, subset=[\"name\", \"title\"]\n", " )\n", " .set_properties(**{\"text-align\": \"center\"}, subset=[\"yearBorn\", \"age\"])\n", " .set_properties(**{\"text-align\": \"right\"}, subset=[\"totalPay\", \"fiscalYear\"])\n", " .format({\"totalPay\": \"{:.2f}\"})\n", " .format({\"yearBorn\": \"{:.0f}\"})\n", " .format({\"age\": \"{:.0f}\"})\n", " .format({\"fiscalYear\": \"{:.0f}\"})\n", " )\n", "\n", " return pn.widgets.Tabulator(company_officers, show_index=False)\n", "\n", "\n", "def get_options_chain(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with options chain\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " ticker = yf.Ticker(ticker)\n", " options_chain = ticker.option_chain()\n", "\n", " # format values of percentChange column to be percentages with 2 decimal places\n", " options_chain.calls[\"percentChange\"] = options_chain.calls[\"percentChange\"].map(\n", " \"{:.2f}%\".format\n", " )\n", "\n", " # format values of impliedVolatility column to be percentages with 2 decimal places\n", " options_chain.calls[\"impliedVolatility\"] = options_chain.calls[\n", " \"impliedVolatility\"\n", " ].map(\"{:.2f}%\".format)\n", "\n", " bokeh_formatters = {\n", " \"bool\": BooleanFormatter(),\n", " }\n", "\n", " return pn.widgets.Tabulator(\n", " options_chain.calls, show_index=False, formatters=bokeh_formatters\n", " )\n", "\n", "\n", "def get_balance_sheet(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with balance sheet\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " ticker = yf.Ticker(ticker)\n", " balance_sheet = ticker.balance_sheet\n", "\n", " # format columns to be dates\n", " balance_sheet.columns = pd.to_datetime(balance_sheet.columns).date\n", "\n", " balance_sheet = balance_sheet.reset_index()\n", "\n", " # rename index column to metric\n", " balance_sheet.rename(columns={\"index\": \"Metric\"}, inplace=True)\n", "\n", " # format index to be left aligned\n", " balance_sheet = balance_sheet.style.set_properties(\n", " **{\"text-align\": \"left\"}, subset=[\"Metric\"]\n", " )\n", " return pn.widgets.Tabulator(balance_sheet, show_index=False)\n", "\n", "\n", "def get_cashflow(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with cashflow\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " ticker = yf.Ticker(ticker)\n", " cashflow = ticker.cashflow\n", "\n", " # format columns to be dates\n", " cashflow.columns = pd.to_datetime(cashflow.columns).date\n", "\n", " cashflow = cashflow.reset_index()\n", "\n", " # rename index column to metric\n", " cashflow.rename(columns={\"index\": \"Metric\"}, inplace=True)\n", "\n", " # format index to be left aligned\n", " cashflow = cashflow.style.set_properties(\n", " **{\"text-align\": \"left\"}, subset=[\"Metric\"]\n", " )\n", " return pn.widgets.Tabulator(cashflow, show_index=False)\n", "\n", "\n", "def get_income_statement(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with income statement\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " ticker = yf.Ticker(ticker)\n", " income_statement = ticker.financials\n", "\n", " # format columns to be dates\n", " income_statement.columns = pd.to_datetime(income_statement.columns).date\n", "\n", " income_statement = income_statement.reset_index()\n", "\n", " # rename index column to metric\n", " income_statement.rename(columns={\"index\": \"Metric\"}, inplace=True)\n", "\n", " # format index to be left aligned\n", " income_statement = income_statement.style.set_properties(\n", " **{\"text-align\": \"left\"}, subset=[\"Metric\"]\n", " )\n", " return pn.widgets.Tabulator(income_statement, show_index=False)\n", " \"\"\"\n", " Create panel with statistics\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " ticker = yf.Ticker(ticker)\n", " statistics = pd.DataFrame(ticker.info)\n", "\n", " # remove unneeded keys\n", " del statistics[\"companyOfficers\"]\n", " del statistics[\"zip\"]\n", " del statistics[\"phone\"]\n", " del statistics[\"longBusinessSummary\"]\n", " del statistics[\"city\"]\n", " del statistics[\"state\"]\n", " del statistics[\"country\"]\n", " del statistics[\"website\"]\n", " del statistics[\"address1\"]\n", " del statistics[\"industry\"]\n", " del statistics[\"sector\"]\n", " del statistics[\"fullTimeEmployees\"]\n", " del statistics[\"maxAge\"]\n", "\n", " # format values of marketCap to be integers\n", " statistics[\"marketCap\"] = f\"{int(statistics['marketCap']):.0f}\"\n", "\n", " # format values of enterpriseValue to be integers\n", " statistics[\"enterpriseValue\"] = f\"{int(statistics['enterpriseValue']):.0f}\"\n", "\n", " # format values of trailingPE to be integers\n", " statistics[\"trailingPE\"] = f\"{float(statistics['trailingPE']):.2f}\"\n", "\n", " # format values of forwardPE to be integers\n", " statistics[\"forwardPE\"] = f\"{float(statistics['forwardPE']):.2f}\"\n", "\n", " # format values of profitMargins to be integers\n", " statistics[\"profitMargins\"] = f\"{float(statistics['profitMargins']):.2f}\"\n", "\n", " # format values of floatShares to be integers\n", " statistics[\"floatShares\"] = f\"{int(statistics['floatShares']):.0f}\"\n", "\n", " # format values of sharesOutstanding to be integers\n", " statistics[\"sharesOutstanding\"] = f\"{int(statistics['sharesOutstanding']):.0f}\"\n", "\n", " # format values of sharesShort to be integers\n", " statistics[\"sharesShort\"] = f\"{int(statistics['sharesShort']):.0f}\"\n", "\n", " # format values of sharesShortPriorMonth to be integers\n", " statistics[\n", " \"sharesShortPriorMonth\"\n", " ] = f\"{int(statistics['sharesShortPriorMonth']):.0f}\"\n", "\n", " # format values of heldPercentInsiders to be integers\n", " statistics[\n", " \"heldPercentInsiders\"\n", " ] = f\"{float(statistics['heldPercentInsiders']):.2f}\"\n", "\n", " # format values of heldPercentInstitutions to be integers\n", " statistics[\n", " \"heldPercentInstitutions\"\n", " ] = f\"{float(statistics['heldPercentInstitutions']):.2f}\"\n", "\n", " # format values of shortRatio to be integers\n", " statistics[\"shortRatio\"] = f\"{float(statistics['shortRatio']):.2f}\"\n", "\n", " # format values of shortPercentOfFloat to be integers\n", " statistics[\n", " \"shortPercentOfFloat\"\n", " ] = f\"{float(statistics['shortPercentOfFloat']):.2f}\"\n", "\n", " # format values of beta to be integers\n", " statistics[\"beta\"] = f\"{float(statistics['beta']):.2f}\"\n", "\n", " # format values of trailingEps to be integers\n", " statistics[\"trailingEps\"] = f\"{float(statistics['trailingEps']):.2f}\"\n", "\n", " # format values of forwardEps to be integers\n", " statistics[\"forwardEps\"] = f\"{float(statistics['forwardEps']):.2f}\"\n", "\n", " # format values of bookValue to be integers\n", " statistics[\"bookValue\"] = f\"{float(statistics['bookValue']):.2f}\"\n", "\n", " # format values of priceToBook to be integers\n", " statistics[\"priceToBook\"] = f\"{float(statistics['priceToBook']):.2f}\"\n", "\n", " # format values of enterpriseToRevenue to be integers\n", " statistics[\n", " \"enterpriseToRevenue\"\n", " ] = f\"{float(statistics['enterpriseToRevenue']):.2f}\"\n", "\n", " # format values of enterpriseToEbitda to be integers\n", " statistics[\"enterpriseToEbitda\"] = f\"{float(statistics['enterpriseToEbitda']):.2f}\"\n", "\n", " return pn.widgets.Tabulator(pd.DataFrame(statistics), show_index=True)\n", "\n", "\n", "def get_ticker_holders(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker holders\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_holders = ticker.major_holders.reset_index()\n", "\n", " # rename index column to holder\n", " ticker_holders.rename(columns={\"index\": \"Holder\"}, inplace=True)\n", "\n", " # format values of Value column to be percentages with 2 decimal places except for the last row\n", " ticker_holders[\"Value\"][:-1] = ticker_holders[\"Value\"][:-1].map(\"{:.2%}\".format)\n", "\n", " return pn.widgets.Tabulator(ticker_holders, show_index=False)\n", "\n", "\n", "def get_ticker_mutual_fund_holders(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker mutual fund holders\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", "\n", " ticker_mutualfund_holders = ticker.mutualfund_holders.reset_index()\n", "\n", " # rearrange columns\n", " ticker_mutualfund_holders = ticker_mutualfund_holders[\n", " [\"Holder\", \"Date Reported\", \"Shares\", \"Value\", \"pctHeld\"]\n", " ]\n", "\n", " # format values of pctHeld to be percentages with 2 decimal places\n", " ticker_mutualfund_holders[\"pctHeld\"] = ticker_mutualfund_holders[\"pctHeld\"].map(\n", " \"{:.2%}\".format\n", " )\n", "\n", " # format values of date reported to be dates\n", " ticker_mutualfund_holders[\"Date Reported\"] = pd.to_datetime(\n", " ticker_mutualfund_holders[\"Date Reported\"]\n", " ).dt.date\n", "\n", " return pn.widgets.Tabulator(ticker_mutualfund_holders, show_index=False)\n", "\n", "\n", "def get_ticker_recommendations(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker recommendations\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_recommendations = ticker.recommendations\n", "\n", " return pn.widgets.Tabulator(ticker_recommendations, show_index=False)\n", "\n", "\n", "def get_ticker_calendar(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker calendar\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_calendar = ticker.calendar\n", "\n", " return pn.widgets.Tabulator(pd.DataFrame(ticker_calendar), show_index=False)\n", "\n", "\n", "def get_ticker_actions(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker actions\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_actions = ticker.actions\n", "\n", " # format index as date strings\n", " ticker_actions.index = ticker_actions.index.strftime(\"%Y-%m-%d\")\n", "\n", " # foramt values of split column to be integers\n", " ticker_actions[\"Stock Splits\"] = ticker_actions[\"Stock Splits\"].map(\"{:.0f}\".format)\n", "\n", " return pn.widgets.Tabulator(ticker_actions)\n", "\n", "\n", "def get_ticker_splits(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker splits\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_splits = ticker.splits\n", "\n", " return pn.widgets.Tabulator(ticker_splits, show_index=False)\n", "\n", "\n", "def get_ticker_sustainability(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker sustainability\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_sustainability = ticker.sustainability\n", "\n", " return pn.widgets.Tabulator(ticker_sustainability, show_index=False)\n", "\n", "\n", "def get_ticker_earnings(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker earnings\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_earnings = ticker.earnings\n", "\n", " return pn.widgets.Tabulator(ticker_earnings, show_index=False)\n", "\n", "\n", "def get_ticker_quarterly_earnings(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker quarterly earnings\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_quarterly_earnings = ticker.quarterly_earnings\n", "\n", " return pn.widgets.Tabulator(ticker_quarterly_earnings, show_index=False)\n", "\n", "\n", "def get_ticker_dividends(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker dividends\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " ticker_dividends = ticker.dividends\n", "\n", " return pn.widgets.Tabulator(pd.DataFrame(ticker_dividends), show_index=False)\n", "\n", "\n", "def get_instiutional_holders(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with ticker quarterly earnings\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", " ticker = yf.Ticker(ticker)\n", " instiutional_holders = ticker.institutional_holders\n", "\n", " # rearrange columns\n", " instiutional_holders = instiutional_holders[\n", " [\"Holder\", \"Date Reported\", \"Shares\", \"Value\", \"pctHeld\"]\n", " ]\n", "\n", " # format values of pctHeld to be percentages with 2 decimal places\n", " instiutional_holders[\"pctHeld\"] = instiutional_holders[\"pctHeld\"].map(\n", " \"{:.2%}\".format\n", " )\n", "\n", " # format values of date reported to be dates\n", " instiutional_holders[\"Date Reported\"] = pd.to_datetime(\n", " instiutional_holders[\"Date Reported\"]\n", " ).dt.date\n", "\n", " # format values of holder column to be left aligned\n", " instiutional_holders = instiutional_holders.style.set_properties(\n", " **{\"text-align\": \"left\"}, subset=[\"Holder\"]\n", " )\n", "\n", " return pn.widgets.Tabulator(instiutional_holders, show_index=False)\n", "\n", "\n", "def get_bollinger_bands_chart(\n", " ticker: str = \"MSFT\",\n", " start_date: str = \"2020-01-01\",\n", " end_date: str = datetime.now().strftime(\"%Y-%m-%d\"),\n", " bb_n: int = 20,\n", " bb_k: int = 2,\n", "):\n", " \"\"\"Returns a Bollinger Bands chart\"\"\"\n", " df = get_historical_data(ticker, start_date, end_date)\n", "\n", " qf = cf.QuantFig(\n", " df,\n", " title=f\"TA Dashboard - {ticker}\",\n", " legend=\"right\",\n", " name=f\"{ticker}\",\n", " showlegend=True,\n", " width=1000,\n", " )\n", "\n", " qf.add_bollinger_bands(periods=bb_n, boll_std=bb_k)\n", "\n", " return qf.iplot(asFigure=True, theme=\"henanigans\")\n", "\n", "\n", "def get_macd_chart(\n", " ticker: str = \"MSFT\",\n", " start_date: str = \"2020-01-01\",\n", " end_date: str = datetime.now().strftime(\"%Y-%m-%d\"),\n", " macd_fast: int = 12,\n", " macd_slow: int = 26,\n", " macd_signal: int = 9,\n", "):\n", " \"\"\"Returns a MACD chart\"\"\"\n", " df = get_historical_data(ticker, start_date, end_date)\n", "\n", " qf = cf.QuantFig(\n", " df,\n", " title=f\"MACD - {ticker}\",\n", " legend=\"right\",\n", " name=f\"{ticker}\",\n", " showlegend=True,\n", " width=1000,\n", " )\n", "\n", " qf.add_macd(fast_period=macd_fast, slow_period=macd_slow, signal_period=macd_signal)\n", "\n", " return qf.iplot(asFigure=True, theme=\"henanigans\")\n", "\n", "\n", "def get_rsi_chart(\n", " ticker: str = \"MSFT\",\n", " start_date: str = \"2020-01-01\",\n", " end_date: str = datetime.now().strftime(\"%Y-%m-%d\"),\n", " rsi_periods: int = 14,\n", " rsi_upper: int = 70,\n", " rsi_lower: int = 30,\n", "):\n", " \"\"\"Returns a RSI chart\"\"\"\n", " df = get_historical_data(ticker, start_date, end_date)\n", "\n", " qf = cf.QuantFig(\n", " df,\n", " title=f\"RSI - {ticker}\",\n", " legend=\"right\",\n", " name=f\"{ticker}\",\n", " showlegend=True,\n", " width=1000,\n", " )\n", "\n", " qf.add_rsi(\n", " periods=rsi_periods, rsi_upper=rsi_upper, rsi_lower=rsi_lower, showbands=True\n", " )\n", "\n", " return qf.iplot(asFigure=True, theme=\"henanigans\")\n", "\n", "\n", "def detect_outliers(\n", " ticker: str = TICKER_DEFAULT, rolling_window: int = 21, n_sigmas: int = 3\n", "):\n", " \"\"\"\n", " Outlier detection using rolling statistics\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " df = get_historical_data(ticker)\n", " df[\"rtn\"] = df[\"Close\"].pct_change()\n", " df = df[[\"rtn\"]].copy()\n", " df_rolling = df.rolling(window=rolling_window).agg([\"mean\", \"std\"])\n", " df_rolling.columns = df_rolling.columns.droplevel()\n", " df = df.join(df_rolling)\n", " df[\"upper\"] = df[\"mean\"] + n_sigmas * df[\"std\"]\n", " df[\"lower\"] = df[\"mean\"] - n_sigmas * df[\"std\"]\n", " df[\"outlier\"] = df.apply(\n", " lambda x: x[\"rtn\"] > x[\"upper\"] or x[\"rtn\"] < x[\"lower\"], axis=1\n", " )\n", "\n", " # plot returns and threshold with markers for outliers\n", " fig, ax = plt.subplots()\n", "\n", " df[[\"rtn\", \"upper\", \"lower\"]].plot(ax=ax)\n", " ax.scatter(\n", " df.loc[df[\"outlier\"]].index,\n", " df.loc[df[\"outlier\"], \"rtn\"],\n", " color=\"red\",\n", " label=\"outlier\",\n", " )\n", " ax.set_title(f\"{ticker}'s stock returns\")\n", " ax.legend(loc=\"center left\", bbox_to_anchor=(1, 0.5))\n", "\n", " sns.despine()\n", " plt.tight_layout()\n", "\n", " return pn.pane.Matplotlib(fig, sizing_mode=\"stretch_width\", fixed_aspect=False)\n", "\n", "\n", "def plot_returns_distribution(df):\n", " \"\"\"\n", " Plot returns distribution\n", "\n", " Parameters\n", " ----------\n", " df : DataFrame\n", " DataFrame with returns\n", " \"\"\"\n", " r_range = np.linspace(min(df[\"log_rtn\"]), max(df[\"log_rtn\"]), num=1000)\n", " mu = df[\"log_rtn\"].mean()\n", " sigma = df[\"log_rtn\"].std()\n", " norm_pdf = scs.norm.pdf(r_range, loc=mu, scale=sigma)\n", "\n", " fig = Figure(figsize=(12, 4))\n", " ax = fig.subplots(1, 2)\n", "\n", " # histogram\n", " sns.distplot(df.log_rtn, kde=False, norm_hist=True, ax=ax[0])\n", " ax[0].set_title(f\"Distribution of returns\", fontsize=16)\n", " ax[0].plot(r_range, norm_pdf, \"g\", lw=2, label=f\"N({mu:.2f}, {sigma**2:.4f})\")\n", " ax[0].legend(loc=\"upper left\")\n", "\n", " # Q-Q plot\n", " qq = sm.qqplot(df.log_rtn.values, line=\"s\", ax=ax[1])\n", " ax[1].set_title(\"Q-Q plot\", fontsize=16)\n", "\n", " sns.despine()\n", " plt.tight_layout()\n", "\n", " return pn.pane.Matplotlib(fig, sizing_mode=\"stretch_width\", fixed_aspect=False)\n", "\n", "\n", "def plot_volatility_clustering(df):\n", " \"\"\"\n", " Plot volatility clustering\n", "\n", " Parameters\n", " ----------\n", " df : DataFrame\n", " DataFrame with returns\n", " \"\"\"\n", "\n", " # create new figure\n", " fig, ax = plt.subplots(1, 1, figsize=(12, 4))\n", "\n", " df[\"log_rtn\"].plot(title=\"Daily returns\", ax=ax)\n", "\n", " sns.despine()\n", " plt.tight_layout()\n", "\n", " return pn.pane.Matplotlib(fig, sizing_mode=\"stretch_width\", fixed_aspect=False)\n", "\n", "\n", "def get_descriptive_stats_table(df):\n", " \"\"\"\n", " Get descriptive statistics table\n", "\n", " Parameters\n", " ----------\n", " df : DataFrame\n", " DataFrame with returns\n", " \"\"\"\n", "\n", " jb_test = scs.jarque_bera(df[\"log_rtn\"].values)\n", "\n", " stats = {\n", " \"Range of dates\": f\"{min(df.index.date)} - {max(df.index.date)}\",\n", " \"Number of observations\": df.shape[0],\n", " \"Mean\": f\"{df.log_rtn.mean():.4f}\",\n", " \"Median\": f\"{df.log_rtn.median():.4f}\",\n", " \"Min\": f\"{df.log_rtn.min():.4f}\",\n", " \"Max\": f\"{df.log_rtn.max():.4f}\",\n", " \"Std deviation\": f\"{df.log_rtn.std():.4f}\",\n", " \"Skewness\": f\"{df.log_rtn.skew():.4f}\",\n", " \"Kurtosis\": f\"{df.log_rtn.kurtosis():.4f}\",\n", " \"Jarque-Bera statistic\": f\"{jb_test[0]:.2f} with p-value: {jb_test[1]:.2f}\",\n", " }\n", "\n", " # create table\n", " stats_df = pd.DataFrame.from_dict(stats, orient=\"index\", columns=[\"Value\"])\n", "\n", " # format table\n", " stats_df = stats_df.style.set_properties(\n", " **{\"text-align\": \"center\"}, subset=[\"Value\"]\n", " )\n", "\n", " return pn.widgets.Tabulator(stats_df, show_index=True)\n", "\n", "\n", "def plot_abscense_of_autocorrelation(df):\n", " \"\"\"\n", " Plot absence of autocorrelation\n", "\n", " Parameters\n", " ----------\n", " df : DataFrame\n", " DataFrame with returns\n", " \"\"\"\n", "\n", " N_LAGS = 50\n", " SIGNIFICANCE_LEVEL = 0.05\n", "\n", " # create new figure\n", " fig, ax = plt.subplots(1, 1, figsize=(12, 4))\n", "\n", " acf = smt.graphics.plot_acf(\n", " df[\"log_rtn\"], lags=N_LAGS, alpha=SIGNIFICANCE_LEVEL, ax=ax\n", " )\n", "\n", " sns.despine()\n", " plt.tight_layout()\n", "\n", " return pn.pane.Matplotlib(fig, sizing_mode=\"stretch_width\", fixed_aspect=False)\n", "\n", "\n", "def plot_small_and_decreasing_autocorrelations(df):\n", " \"\"\"\n", " Plot small and decreasing autocorrelations\n", "\n", " Parameters\n", " ----------\n", " df : DataFrame\n", " DataFrame with returns\n", " \"\"\"\n", "\n", " fig, ax = plt.subplots(2, 1, figsize=(12, 6))\n", " SIGNIFICANCE_LEVEL = 0.05\n", " N_LAGS = 50\n", "\n", " smt.graphics.plot_acf(\n", " df[\"log_rtn\"] ** 2, lags=N_LAGS, alpha=SIGNIFICANCE_LEVEL, ax=ax[0]\n", " )\n", " ax[0].set(title=\"Autocorrelation Plots\", ylabel=\"Squared Returns\")\n", "\n", " smt.graphics.plot_acf(\n", " np.abs(df[\"log_rtn\"]), lags=N_LAGS, alpha=SIGNIFICANCE_LEVEL, ax=ax[1]\n", " )\n", " ax[1].set(ylabel=\"Absolute Returns\", xlabel=\"Lag\")\n", "\n", " sns.despine()\n", " plt.tight_layout()\n", "\n", " return pn.pane.Matplotlib(fig, sizing_mode=\"stretch_width\", fixed_aspect=False)\n", "\n", "\n", "def plot_leverage_effect(df):\n", " \"\"\"\n", " Plot leverage effect\n", "\n", " Parameters\n", " ----------\n", " df : DataFrame\n", " DataFrame with returns\n", " \"\"\"\n", "\n", " df[\"rolling_std_252\"] = df[[\"log_rtn\"]].rolling(window=252).std()\n", " df[\"rolling_std_21\"] = df[[\"log_rtn\"]].rolling(window=21).std()\n", "\n", " fig, ax = plt.subplots(3, 1, sharex=True, figsize=(12, 9))\n", "\n", " df[\"Close\"].plot(ax=ax[0])\n", " ax[0].set(title=\"Price time series\", ylabel=\"Price ($)\")\n", "\n", " df[\"log_rtn\"].plot(ax=ax[1])\n", " ax[1].set(ylabel=\"Log returns\")\n", "\n", " df[\"rolling_std_252\"].plot(ax=ax[2], color=\"r\", label=\"Rolling Volatility 252d\")\n", " df[\"rolling_std_21\"].plot(ax=ax[2], color=\"g\", label=\"Rolling Volatility 21d\")\n", " ax[2].set(ylabel=\"Moving Volatility\", xlabel=\"Date\")\n", " ax[2].legend()\n", "\n", " sns.despine()\n", " plt.tight_layout()\n", "\n", " return pn.pane.Matplotlib(fig, sizing_mode=\"stretch_width\", fixed_aspect=False)\n", "\n", "\n", "def get_stylized_facts_charts(ticker: str = TICKER_DEFAULT):\n", " \"\"\"\n", " Create panel with stylized facts charts. The stylized facts are:\n", " 1. Non-Gaussian distribution of returns\n", " 2. Volatility clustering\n", " 3. Absence of autocorrelation\n", " 4. Small and decreasing autocorrelations of squared and absolute returns\n", " 5. Leverage effect\n", "\n", " Parameters\n", " ----------\n", " ticker : str\n", " The ticker symbol for the company\n", " \"\"\"\n", "\n", " df = get_historical_data(ticker)\n", " df[\"log_rtn\"] = np.log(df[\"Close\"] / df[\"Close\"].shift(1))\n", " df = df[[\"Close\", \"log_rtn\"]].dropna()\n", "\n", " # Non-Gaussian distribution of returns\n", " fig1 = plot_returns_distribution(df)\n", " fig2 = get_descriptive_stats_table(df)\n", "\n", " # Volatility clustering\n", " fig3 = plot_volatility_clustering(df)\n", "\n", " # Absence of autocorrelation\n", " fig4 = plot_abscense_of_autocorrelation(df)\n", "\n", " # Small and decreasing autocorrelations of squared and absolute returns\n", " fig5 = plot_small_and_decreasing_autocorrelations(df)\n", "\n", " # Leverage effect\n", " fig6 = plot_leverage_effect(df)\n", "\n", " return pn.Column(\n", " pn.pane.Markdown(\"## 1. Non-Gaussian distribution of returns\"),\n", " fig1,\n", " pn.pane.Markdown(\"### Descriptive statistics\"),\n", " fig2,\n", " pn.pane.Markdown(\"## 2. Volatility clustering\"),\n", " fig3,\n", " pn.pane.Markdown(\"## 3. Absence of autocorrelation\"),\n", " fig4,\n", " pn.pane.Markdown(\n", " \"## 4. Small and decreasing autocorrelations of squared and absolute returns\"\n", " ),\n", " fig5,\n", " pn.pane.Markdown(\"## 5. Leverage effect\"),\n", " fig6,\n", " )" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [], "source": [ "asset_selector = pn.widgets.Select(name=\"Asset\", options=stocks, value=\"MSFT\")\n", "\n", "start_date_selector = pn.widgets.DatePicker(\n", " name=\"Start Date\", value=pd.to_datetime(\"2023-01-01\").date()\n", ")\n", "\n", "end_date_selector = pn.widgets.DatePicker(\n", " name=\"End Date\", value=pd.to_datetime(\"today\").date()\n", ")\n", "\n", "main_box = pn.WidgetBox(\n", " \"### Main Parameters\",\n", " asset_selector,\n", " start_date_selector,\n", " end_date_selector,\n", ")\n", "\n", "n_param = pn.widgets.IntSlider(name=\"N\", value=20, start=1, end=40, step=1)\n", "k_param = pn.widgets.FloatSlider(name=\"k\", value=2, start=0.5, end=4, step=0.5)\n", "\n", "bollinger_box = pn.WidgetBox(\"### Bollinger Bands\", n_param, k_param, width=400)\n", "\n", "macd_fast = pn.widgets.IntSlider(\n", " name=\"Fast Moving Average:\", value=12, start=2, end=50, step=1, width=200\n", ")\n", "\n", "macd_slow = pn.widgets.IntSlider(\n", " name=\"Slow Moving Average:\", value=26, start=2, end=50, step=1\n", ")\n", "\n", "macd_signal = pn.widgets.IntSlider(\n", " name=\"Signal Line:\", value=9, start=2, end=50, step=1\n", ")\n", "\n", "macd_box = pn.WidgetBox(\"### MACD\", macd_fast, macd_slow, macd_signal, width=400)\n", "\n", "rsi_periods = pn.widgets.IntSlider(name=\"Periods\", value=14, start=2, end=50, step=1)\n", "\n", "rsi_upper = pn.widgets.IntSlider(\n", " name=\"Upper Threshold\", value=70, start=1, end=100, step=1\n", ")\n", "\n", "rsi_lower = pn.widgets.IntSlider(\n", " name=\"Lower Threshold\", value=30, start=1, end=100, step=1\n", ")\n", "\n", "rsi_box = pn.WidgetBox(\"### RSI\", rsi_periods, rsi_upper, rsi_lower, width=400)\n", "\n", "indicator_box = pn.WidgetBox(\n", " \"### Indicator Parameters\", bollinger_box, macd_box, rsi_box\n", ")\n", "\n", "param_box = pn.Column(main_box, indicator_box)\n", "\n", "bound_line_chart = pn.bind(\n", " get_line_chart,\n", " ticker=asset_selector,\n", " start_date=start_date_selector,\n", " end_date=end_date_selector,\n", ")\n", "\n", "bound_candlestick_chart = pn.bind(\n", " get_candlestick_chart,\n", " ticker=asset_selector,\n", " start_date=start_date_selector,\n", " end_date=end_date_selector,\n", ")\n", "\n", "bound_bollinger_bands_chart = pn.bind(\n", " get_bollinger_bands_chart,\n", " ticker=asset_selector,\n", " start_date=start_date_selector,\n", " end_date=end_date_selector,\n", " bb_n=n_param,\n", " bb_k=k_param,\n", ")\n", "\n", "bound_macd_chart = pn.bind(\n", " get_macd_chart,\n", " ticker=asset_selector,\n", " start_date=start_date_selector,\n", " end_date=end_date_selector,\n", " macd_fast=macd_fast,\n", " macd_slow=macd_slow,\n", " macd_signal=macd_signal,\n", ")\n", "\n", "bound_rsi_chart = pn.bind(\n", " get_rsi_chart,\n", " ticker=asset_selector,\n", " start_date=start_date_selector,\n", " end_date=end_date_selector,\n", " rsi_periods=rsi_periods,\n", " rsi_upper=rsi_upper,\n", " rsi_lower=rsi_lower,\n", ")\n", "\n", "charts = pn.Column(\n", " bound_candlestick_chart,\n", " bound_bollinger_bands_chart,\n", " bound_macd_chart,\n", " bound_rsi_chart,\n", " width=800,\n", ")\n", "\n", "\n", "rolling_window_slider = pn.widgets.IntSlider(\n", " name=\"Rolling Window\", value=21, start=1, end=40, step=1\n", ")\n", "n_sigmas_slider = pn.widgets.FloatSlider(\n", " name=\"N of Sigmas\", value=3, start=0.5, end=4, step=0.5\n", ")\n", "\n", "detect_outliers_box = pn.WidgetBox(\n", " \"### Outlier Detection Using Rolling Stats\",\n", " rolling_window_slider,\n", " n_sigmas_slider,\n", " width=400,\n", ")\n", "\n", "bound_outlier_detection = pn.bind(\n", " detect_outliers,\n", " ticker=asset_selector,\n", " rolling_window=rolling_window_slider,\n", " n_sigmas=n_sigmas_slider,\n", ")\n", "\n", "bound_stylized_facts_charts = pn.bind(\n", " get_stylized_facts_charts,\n", " ticker=asset_selector,\n", ")\n", "\n", "dashboard = pn.Row(param_box, charts, width=1400)\n", "\n", "price_row = pn.bind(get_close_price_info, ticker=asset_selector)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'price_row' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[41], line 5\u001b[0m\n\u001b[1;32m 1\u001b[0m tabs \u001b[38;5;241m=\u001b[39m pn\u001b[38;5;241m.\u001b[39mTabs(\n\u001b[1;32m 2\u001b[0m (\n\u001b[1;32m 3\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mOverview\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 4\u001b[0m pn\u001b[38;5;241m.\u001b[39mColumn(\n\u001b[0;32m----> 5\u001b[0m \u001b[43mprice_row\u001b[49m, \n\u001b[1;32m 6\u001b[0m pn\u001b[38;5;241m.\u001b[39mbind(get_business_summary, ticker\u001b[38;5;241m=\u001b[39masset_selector), \n\u001b[1;32m 7\u001b[0m bound_candlestick_chart\n\u001b[1;32m 8\u001b[0m ),\n\u001b[1;32m 9\u001b[0m ),\n\u001b[1;32m 10\u001b[0m (\n\u001b[1;32m 11\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTechnicals\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 12\u001b[0m pn\u001b[38;5;241m.\u001b[39mColumn(\n\u001b[1;32m 13\u001b[0m pn\u001b[38;5;241m.\u001b[39mRow(bollinger_box, bound_bollinger_bands_chart),\n\u001b[1;32m 14\u001b[0m pn\u001b[38;5;241m.\u001b[39mRow(macd_box, bound_macd_chart),\n\u001b[1;32m 15\u001b[0m pn\u001b[38;5;241m.\u001b[39mRow(rsi_box, bound_rsi_chart),\n\u001b[1;32m 16\u001b[0m ),\n\u001b[1;32m 17\u001b[0m ),\n\u001b[1;32m 18\u001b[0m )\n", "\u001b[0;31mNameError\u001b[0m: name 'price_row' is not defined" ] } ], "source": [ "tabs = pn.Tabs(\n", " (\n", " \"Overview\",\n", " pn.Column(\n", " price_row,\n", " pn.pane.Markdown(\"## Summary Detail\"),\n", " pn.bind(get_business_summary, ticker=asset_selector),\n", " pn.pane.Markdown(\"## Company Officers\"),\n", " pn.bind(get_company_officers, ticker=asset_selector),\n", " pn.pane.Markdown(\"## Holders\"),\n", " pn.bind(get_ticker_holders, ticker=asset_selector),\n", " pn.pane.Markdown(\"## Institutional Holders\"),\n", " pn.bind(get_instiutional_holders, ticker=asset_selector),\n", " pn.pane.Markdown(\"## Mutual Fund Holders\"),\n", " pn.bind(get_ticker_mutual_fund_holders, ticker=asset_selector),\n", " bound_candlestick_chart,\n", " ),\n", " ),\n", " (\n", " \"Financials\",\n", " pn.Column(\n", " pn.pane.Markdown(\"## Financials\"),\n", " pn.Tabs(\n", " (\n", " \"Income Statement\",\n", " pn.Column(\n", " pn.pane.Markdown(\"## Income Statement\"),\n", " pn.bind(get_income_statement, ticker=asset_selector),\n", " ),\n", " ),\n", " (\n", " \"Cashflow\",\n", " pn.Column(\n", " pn.pane.Markdown(\"## Cashflow\"),\n", " pn.bind(get_cashflow, ticker=asset_selector),\n", " ),\n", " ),\n", " (\n", " \"Balance Sheet\",\n", " pn.Column(\n", " pn.pane.Markdown(\"## Balance Sheet\"),\n", " pn.bind(get_balance_sheet, ticker=asset_selector),\n", " ),\n", " ),\n", " ),\n", " pn.pane.Markdown(\"## Calendar\"),\n", " pn.bind(get_ticker_calendar, ticker=asset_selector),\n", " pn.pane.Markdown(\"## Recommendations\"),\n", " pn.bind(get_ticker_recommendations, ticker=asset_selector),\n", " pn.pane.Markdown(\"## Actions\"),\n", " pn.bind(get_ticker_actions, ticker=asset_selector),\n", " ),\n", " ),\n", " (\n", " \"Technicals\",\n", " pn.Column(\n", " pn.Row(bollinger_box, bound_bollinger_bands_chart),\n", " pn.Row(macd_box, bound_macd_chart),\n", " pn.Row(rsi_box, bound_rsi_chart),\n", " ),\n", " ),\n", " (\n", " \"Options\",\n", " pn.Column(\n", " pn.pane.Markdown(\"## Options Chain\"),\n", " pn.bind(get_options_chain, ticker=asset_selector),\n", " ),\n", " ),\n", " (\"Sustainability\", pn.pane.Alert(\"Coming soon...\", alert_type=\"warning\")),\n", " (\n", " \"Outliers\",\n", " pn.Column(\n", " pn.Row(detect_outliers_box, bound_outlier_detection),\n", " ),\n", " ),\n", " (\n", " \"Stylized Facts\",\n", " bound_stylized_facts_charts,\n", " ),\n", ")" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [], "source": [ "# Define the template\n", "ACCENT = \"#BB2649\"\n", "\n", "template = pn.template.FastGridTemplate(\n", " title=\"Stock Analysis Dashboard - standa\",\n", " accent_base_color=ACCENT,\n", " header_background=ACCENT,\n", " prevent_collision=True,\n", " save_layout=True,\n", " theme_toggle=False,\n", " theme=\"dark\",\n", " row_height=160,\n", " favicon=\"standa.ico\",\n", ")" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [], "source": [ "template.main[0:3, 0:3] = main_box\n", "template.main[0:3, 3:12] = bound_line_chart\n", "template.main[3:12, :] = tabs" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "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", "\t
\n", "
\n", " \n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
\n", "
\n", "\n", " \n", " \n", " \n" ], "text/plain": [ "FastGridTemplate\n", " [js_area] HTML(None, height=0, margin=0, sizing_mode='fixed', width=0)\n", " [actions] TemplateActions()\n", " [browser_info] BrowserInfo()\n", " [busy_indicator] LoadingSpinner(height=20, width=20)\n", " [main-11325653984] ParamFunction(function, _pane=Plotly, defer_load=False, sizing_mode='stretch_width')\n", " [main-11377809488] ParamFunction(function, _pane=Plotly, defer_load=False, sizing_mode='stretch_width')\n", " [main-11409973392] Column(sizing_mode='stretch_width')\n", " [0] WidgetBox(sizing_mode='stretch_width')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] Select(name='Asset', options=['MSFT', 'GOOGL', ...], sizing_mode='stretch_width', value='MSFT')\n", " [2] DatePicker(name='Start Date', sizing_mode='stretch_width', value=datetime.date(2020, 1, 1))\n", " [3] DatePicker(name='End Date', sizing_mode='stretch_width', value=datetime.date(2024, ...)\n", " [1] WidgetBox(sizing_mode='stretch_width')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] WidgetBox(sizing_mode='stretch_width')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] IntSlider(end=40, name='N', sizing_mode='stretch_width', start=1, value=20)\n", " [2] FloatSlider(end=4, name='k', sizing_mode='stretch_width', start=0.5, step=0.5, value=2)\n", " [2] WidgetBox(sizing_mode='stretch_width')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] IntSlider(end=50, name='Fast Moving Average:', sizing_mode='stretch_width', start=2, value=12)\n", " [2] IntSlider(end=50, name='Slow Moving Average:', sizing_mode='stretch_width', start=2, value=26)\n", " [3] IntSlider(end=50, name='Signal Line:', sizing_mode='stretch_width', start=2, value=9)\n", " [3] WidgetBox(sizing_mode='stretch_width')\n", " [0] Markdown(str, sizing_mode='stretch_width')\n", " [1] IntSlider(end=50, name='Periods', sizing_mode='stretch_width', start=2, value=14)\n", " [2] IntSlider(end=100, name='Upper Threshold', sizing_mode='stretch_width', start=1, value=70)\n", " [3] IntSlider(end=100, name='Lower Threshold', sizing_mode='stretch_width', start=1, value=30)" ] }, "execution_count": 75, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "76b36f11-0af9-4ccb-981b-94d0d7dfba2c" } }, "output_type": "execute_result" } ], "source": [ "template.servable()" ] } ], "metadata": { "kernelspec": { "display_name": "finground", "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.12.1" } }, "nbformat": 4, "nbformat_minor": 2 }