{"spec_id":"depth-order-book","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ndepth-order-book: Order Book Depth Chart\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 88/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_area,\n    geom_vline,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nRULE = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — semantic exception: bid=green (buy/gain), ask=red (sell/loss)\nBID_COLOR = \"#009E73\"  # Imprint position 1 — green for bids\nASK_COLOR = \"#AE3030\"  # Imprint position 5 — red for asks (semantic: loss/sell)\n\n# Theme-adaptive fill alpha: higher in dark mode keeps ask area legible on near-black bg\nFILL_ALPHA = 0.30 if THEME == \"light\" else 0.42\n\n# Data — synthetic BTC/USD order book snapshot, mid price ~60,000\nnp.random.seed(42)\n\nMID_PRICE = 60_000.0\nSPREAD = 10.0  # $10 bid-ask spread\nN_LEVELS = 50  # price levels per side\nTICK = 10.0  # price increment per level\n\nbest_bid = MID_PRICE - SPREAD / 2\nbid_prices = best_bid - np.arange(N_LEVELS) * TICK\n\nbest_ask = MID_PRICE + SPREAD / 2\nask_prices = best_ask + np.arange(N_LEVELS) * TICK\n\n# Quantities grow away from mid price; add liquidity walls for realism\nbid_quantities = np.random.exponential(scale=1.5, size=N_LEVELS) + 0.3 * np.arange(N_LEVELS) / N_LEVELS * 10\nask_quantities = np.random.exponential(scale=1.5, size=N_LEVELS) + 0.3 * np.arange(N_LEVELS) / N_LEVELS * 10\nbid_quantities[12] += 18.0\nask_quantities[8] += 14.0\n\nbid_cumulative = np.cumsum(bid_quantities)\nask_cumulative = np.cumsum(ask_quantities)\n\n# Build staircase (x ascending) by interleaving each price with the next price\n# at the same y, creating horizontal steps before y jumps at the next level.\n# Pattern: (p[i], c[i]), (p[i+1], c[i]), (p[i+1], c[i+1]), ...\n\n# Asks: prices already ascending (60005 → 60495)\nask_x = np.empty(2 * N_LEVELS - 1)\nask_x[0::2] = ask_prices\nask_x[1::2] = ask_prices[1:]\nask_y = np.empty(2 * N_LEVELS - 1)\nask_y[0::2] = ask_cumulative\nask_y[1::2] = ask_cumulative[:-1]\n\n# Bids: prices are descending (59995 → 59505) — reverse to ascending first\nbid_prices_asc = bid_prices[::-1]\nbid_cums_asc = bid_cumulative[::-1]\nbid_x = np.empty(2 * N_LEVELS - 1)\nbid_x[0::2] = bid_prices_asc\nbid_x[1::2] = bid_prices_asc[1:]\nbid_y = np.empty(2 * N_LEVELS - 1)\nbid_y[0::2] = bid_cums_asc\nbid_y[1::2] = bid_cums_asc[:-1]\n\n# \"side\" column maps fill aesthetic to legend entries\nbid_df = pd.DataFrame({\"price\": bid_x, \"cum_qty\": bid_y, \"side\": \"Bids\"})\nask_df = pd.DataFrame({\"price\": ask_x, \"cum_qty\": ask_y, \"side\": \"Asks\"})\n\n# Title font scaling\ntitle = \"depth-order-book · python · letsplot · anyplot.ai\"\ndefault_title_size = 16\nn_chars = len(title)\ntitle_size = round(default_title_size * 67 / n_chars) if n_chars > 67 else default_title_size\ntitle_size = max(title_size, 11)\n\nmid_label = \"BTC/USD — Mid: $60,000  |  Spread: $10\"\n\n# Interactive tooltips for HTML export — letsplot-specific feature\nbid_tooltip = layer_tooltips().line(\"Bids\").line(\"Price|@{price}\").line(\"Cum. Vol.|@{cum_qty} BTC\")\nask_tooltip = layer_tooltips().line(\"Asks\").line(\"Price|@{price}\").line(\"Cum. Vol.|@{cum_qty} BTC\")\n\n# anyplot theme — panel_border=blank + axis_line gives L-shaped frame (top/right removed)\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_border=element_blank(),\n    panel_grid_major=element_line(color=RULE, size=0.3),\n    panel_grid_major_x=element_blank(),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=title_size),\n    plot_subtitle=element_text(color=INK_MUTED, size=10),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=10),\n    legend_title=element_blank(),\n)\n\n# Plot — fill mapped to \"side\" drives the legend; fixed color keeps outlines solid\nplot = (\n    ggplot()\n    + geom_area(\n        mapping=aes(x=\"price\", y=\"cum_qty\", fill=\"side\"),\n        data=bid_df,\n        color=BID_COLOR,\n        alpha=FILL_ALPHA,\n        size=0.8,\n        tooltips=bid_tooltip,\n    )\n    + geom_area(\n        mapping=aes(x=\"price\", y=\"cum_qty\", fill=\"side\"),\n        data=ask_df,\n        color=ASK_COLOR,\n        alpha=FILL_ALPHA,\n        size=0.8,\n        tooltips=ask_tooltip,\n    )\n    + geom_vline(xintercept=MID_PRICE, color=INK_MUTED, size=0.6, linetype=\"dashed\")\n    + scale_fill_manual(values={\"Bids\": BID_COLOR, \"Asks\": ASK_COLOR}, breaks=[\"Bids\", \"Asks\"])\n    + scale_x_continuous(name=\"Price (USD)\")\n    + scale_y_continuous(name=\"Cumulative Volume (BTC)\", limits=[0, None])\n    + labs(title=title, subtitle=mid_label)\n    + anyplot_theme\n    + ggsize(800, 450)\n)\n\n# Save — use path=\".\" so files land in the working directory, not lets-plot-images/\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}