{"spec_id":"indicator-rsi","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nindicator-rsi: RSI Technical Indicator Chart\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport sys\nfrom importlib.machinery import SourceFileLoader\n\nimport numpy as np\nimport pandas as pd\n\n\nvenv_path = sys.executable\nsite_packages = os.path.join(os.path.dirname(venv_path), \"..\", \"lib\", \"python3.13\", \"site-packages\")\naltair_init = os.path.join(site_packages, \"altair\", \"__init__.py\")\n\nloader = SourceFileLoader(\"altair\", altair_init)\nalt = loader.load_module()\n\n\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\"\n\nnp.random.seed(42)\nn_periods = 140\ndates = pd.date_range(start=\"2024-01-01\", periods=n_periods, freq=\"D\")\n\nprice_changes = np.random.randn(n_periods) * 4\n\nlookback = 14\ngains = np.zeros(n_periods)\nlosses = np.zeros(n_periods)\n\nfor i in range(1, n_periods):\n    change = price_changes[i]\n    if change > 0:\n        gains[i] = change\n    else:\n        losses[i] = abs(change)\n\navg_gain = np.zeros(n_periods)\navg_loss = np.zeros(n_periods)\navg_gain[lookback] = np.mean(gains[1 : lookback + 1])\navg_loss[lookback] = np.mean(losses[1 : lookback + 1])\n\nfor i in range(lookback + 1, n_periods):\n    avg_gain[i] = (avg_gain[i - 1] * (lookback - 1) + gains[i]) / lookback\n    avg_loss[i] = (avg_loss[i - 1] * (lookback - 1) + losses[i]) / lookback\n\nwith np.errstate(divide=\"ignore\", invalid=\"ignore\"):\n    rs = np.where(avg_loss != 0, avg_gain / avg_loss, 0)\n    rsi = np.where(avg_loss != 0, 100 - (100 / (1 + rs)), 100)\nrsi[:lookback] = 50\n\ndf = pd.DataFrame({\"date\": dates, \"rsi\": rsi})\n\noverbought_df = pd.DataFrame({\"y\": [70], \"y2\": [100]})\noversold_df = pd.DataFrame({\"y\": [0], \"y2\": [30]})\n\noverbought_zone = (\n    alt.Chart(overbought_df)\n    .mark_rect(opacity=0.12, color=\"#E74C3C\")\n    .encode(y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[0, 100])), y2=alt.Y2(\"y2:Q\"))\n)\n\noversold_zone = (\n    alt.Chart(oversold_df)\n    .mark_rect(opacity=0.12, color=\"#27AE60\")\n    .encode(y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[0, 100])), y2=alt.Y2(\"y2:Q\"))\n)\n\nthreshold_df = pd.DataFrame({\"y\": [30, 50, 70], \"label\": [\"Oversold (30)\", \"Neutral (50)\", \"Overbought (70)\"]})\n\nthreshold_lines = (\n    alt.Chart(threshold_df)\n    .mark_rule(strokeDash=[8, 4], strokeWidth=2)\n    .encode(\n        y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[0, 100])),\n        color=alt.Color(\n            \"label:N\",\n            scale=alt.Scale(\n                domain=[\"Oversold (30)\", \"Neutral (50)\", \"Overbought (70)\"], range=[\"#27AE60\", INK_SOFT, \"#E74C3C\"]\n            ),\n            legend=alt.Legend(\n                title=\"Thresholds\",\n                orient=\"bottom-left\",\n                titleFontSize=16,\n                labelFontSize=14,\n                fillColor=ELEVATED_BG,\n                strokeColor=INK_SOFT,\n            ),\n        ),\n    )\n)\n\nrsi_line = (\n    alt.Chart(df)\n    .mark_line(strokeWidth=3, color=\"#4467A3\")\n    .encode(\n        x=alt.X(\"date:T\", title=\"Date\", axis=alt.Axis(format=\"%b %Y\")),\n        y=alt.Y(\"rsi:Q\", title=\"RSI Value\", scale=alt.Scale(domain=[0, 100])),\n        tooltip=[\n            alt.Tooltip(\"date:T\", title=\"Date\", format=\"%Y-%m-%d\"),\n            alt.Tooltip(\"rsi:Q\", title=\"RSI\", format=\".1f\"),\n        ],\n    )\n)\n\nchart = (\n    alt.layer(overbought_zone, oversold_zone, threshold_lines, rsi_line)\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\n            \"indicator-rsi · altair · anyplot.ai\",\n            fontSize=28,\n            anchor=\"middle\",\n            subtitle=\"14-Period RSI with Overbought/Oversold Zones\",\n            subtitleFontSize=18,\n        ),\n        background=PAGE_BG,\n    )\n    .configure_axis(\n        labelFontSize=18,\n        titleFontSize=22,\n        gridOpacity=0.10,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n    .configure_title(color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n)\n\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}