{"spec_id":"indicator-rsi","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nindicator-rsi: RSI Technical Indicator Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\n\n\n# Clear the local module from sys.modules if it was somehow loaded\nif \"pygal\" in sys.modules and \"implementations\" in sys.modules[\"pygal\"].__file__:\n    del sys.modules[\"pygal\"]\n\n# Remove current directory from path to prevent local file from being imported\n_original_path = sys.path[:]\nsys.path = [p for p in sys.path if p not in (\"\", \".\", os.getcwd())]\n\ntry:\n    import pygal\n    from pygal.style import Style\nfinally:\n    # Restore path\n    sys.path = _original_path\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data - Generate realistic RSI values over 120 trading days\nnp.random.seed(42)\n\nn_days = 120\nlookback = 14\n\n# Generate price changes that produce RSI entering both overbought (>70) and oversold (<30) zones\nbase_changes = np.random.randn(n_days) * 0.8\n\n# Strong uptrend periods (push RSI above 70)\nbase_changes[15:32] += 4.0\nbase_changes[75:92] += 4.5\n\n# Strong downtrend periods (push RSI below 30)\nbase_changes[40:57] -= 4.0\nbase_changes[100:115] -= 3.5\n\nprice_changes = base_changes\n\n# Calculate RSI using exponential moving average\ngains = np.where(price_changes > 0, price_changes, 0)\nlosses = np.where(price_changes < 0, -price_changes, 0)\n\n# Initialize EMA\navg_gain = np.zeros(n_days)\navg_loss = np.zeros(n_days)\n\n# First average\navg_gain[lookback - 1] = np.mean(gains[:lookback])\navg_loss[lookback - 1] = np.mean(losses[:lookback])\n\n# EMA for subsequent values\nalpha = 1 / lookback\nfor i in range(lookback, n_days):\n    avg_gain[i] = alpha * gains[i] + (1 - alpha) * avg_gain[i - 1]\n    avg_loss[i] = alpha * losses[i] + (1 - alpha) * avg_loss[i - 1]\n\n# Calculate RSI (avoid division by zero)\nwith np.errstate(divide=\"ignore\", invalid=\"ignore\"):\n    rs = np.divide(avg_gain, avg_loss, out=np.full_like(avg_gain, 100.0), where=avg_loss > 0)\nrsi = 100 - (100 / (1 + rs))\nrsi[:lookback] = 50\n\n# Okabe-Ito palette with brand green as first series\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Custom style for theme-adaptive rendering\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=28,\n    label_font_size=18,\n    major_label_font_size=16,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=3,\n)\n\n# Create chart\nchart = pygal.Line(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"indicator-rsi · pygal · anyplot.ai\",\n    x_title=\"Trading Period (120 days, 14-period RSI lookback)\",\n    y_title=\"RSI Value (0-100)\",\n    show_dots=False,\n    show_x_guides=False,\n    show_y_guides=True,\n    range=(0, 100),\n    interpolate=\"cubic\",\n    legend_at_bottom=True,\n    legend_box_size=40,\n    margin=60,\n    margin_bottom=180,\n    show_x_labels=False,\n)\n\n# Add threshold lines with colorblind-safe styling\noverbought_line = [70] * n_days\noversold_line = [30] * n_days\ncenterline = [50] * n_days\n\n# Overbought threshold (using Okabe-Ito position 2 - vermillion)\nchart.add(\"Overbought (70)\", overbought_line, stroke_style={\"width\": 4, \"dasharray\": \"20,10\"}, show_dots=False)\n\n# Oversold threshold (using Okabe-Ito position 3 - blue)\nchart.add(\"Oversold (30)\", oversold_line, stroke_style={\"width\": 4, \"dasharray\": \"20,10\"}, show_dots=False)\n\n# Centerline (using muted ink color)\nchart.add(\"Centerline (50)\", centerline, stroke_style={\"width\": 2, \"dasharray\": \"10,10\"}, show_dots=False)\n\n# Add RSI data last so it appears on top with thicker line (brand green - first series)\nchart.add(\"RSI (14)\", list(rsi), stroke_style={\"width\": 6}, show_dots=False)\n\n# Save as PNG and HTML with theme-suffixed filenames\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}