{"spec_id":"kagi-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nkagi-basic: Basic Kagi Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-17\n\"\"\"\n\nfrom __future__ import absolute_import\n\nimport os\nimport sys\n\n\n# Ensure we import the pygal library, not the local script\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p != script_dir and p != \"\"]\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\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_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Okabe-Ito palette - yang (bullish) uses brand green, yin (bearish) uses vermillion\nYANG_COLOR = \"#009E73\"  # Okabe-Ito position 1 - brand green\nYIN_COLOR = \"#AE3030\"  # imprint red — bearish\nYANG_WIDTH = 18  # Thick for yang\nYIN_WIDTH = 4  # Thin for yin\n\n# Data - Generate synthetic stock price data\nnp.random.seed(42)\nn_days = 300  # More days for richer Kagi pattern\nreturns = np.random.normal(0.0006, 0.02, n_days)\nprices = 100 * np.cumprod(1 + returns)\n\n# Kagi chart calculation with 4% reversal threshold\nreversal_pct = 0.04\n\n# Build Kagi chart columns\ncolumns = []\ncurrent_direction = \"up\"\nlast_high = prices[0]\nlast_low = prices[0]\nprev_swing_high = prices[0]\nprev_swing_low = prices[0]\ncol_idx = 0\n\ncurrent_col = {\"x\": col_idx, \"start\": prices[0], \"end\": prices[0], \"type\": \"yang\"}\n\nfor price in prices[1:]:\n    if current_direction == \"up\":\n        if price > last_high:\n            last_high = price\n            current_col[\"end\"] = price\n            if price > prev_swing_high:\n                current_col[\"type\"] = \"yang\"\n        elif price < last_high * (1 - reversal_pct):\n            columns.append(current_col)\n            prev_swing_high = last_high\n            col_idx += 1\n            is_yin = price < prev_swing_low\n            current_col = {\n                \"x\": col_idx,\n                \"start\": columns[-1][\"end\"],\n                \"end\": price,\n                \"type\": \"yin\" if is_yin else columns[-1][\"type\"],\n            }\n            current_direction = \"down\"\n            last_low = price\n    else:\n        if price < last_low:\n            last_low = price\n            current_col[\"end\"] = price\n            if price < prev_swing_low:\n                current_col[\"type\"] = \"yin\"\n        elif price > last_low * (1 + reversal_pct):\n            columns.append(current_col)\n            prev_swing_low = last_low\n            col_idx += 1\n            is_yang = price > prev_swing_high\n            current_col = {\n                \"x\": col_idx,\n                \"start\": columns[-1][\"end\"],\n                \"end\": price,\n                \"type\": \"yang\" if is_yang else columns[-1][\"type\"],\n            }\n            current_direction = \"up\"\n            last_high = price\n\ncolumns.append(current_col)\n\n# Build individual line segments for proper Kagi rendering\nyang_segments = []  # List of [(x1,y1), (x2,y2)] for yang\nyin_segments = []  # List of [(x1,y1), (x2,y2)] for yin\n\nprev_x = None\nprev_y = None\n\nfor col in columns:\n    x = col[\"x\"]\n    y_start = col[\"start\"]\n    y_end = col[\"end\"]\n    seg_type = col[\"type\"]\n\n    # Horizontal connector (shoulder/waist) from previous column\n    if prev_x is not None and prev_x != x:\n        h_segment = [(prev_x, prev_y), (x, prev_y)]\n        if seg_type == \"yang\":\n            yang_segments.append(h_segment)\n        else:\n            yin_segments.append(h_segment)\n\n    # Vertical line segment\n    v_segment = [(x, y_start), (x, y_end)]\n    if seg_type == \"yang\":\n        yang_segments.append(v_segment)\n    else:\n        yin_segments.append(v_segment)\n\n    prev_x = x\n    prev_y = y_end\n\n# Custom style with theme-adaptive colors\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=(YANG_COLOR, YIN_COLOR),\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    value_font_size=14,\n    guide_stroke_dasharray=\"4,4\",\n    opacity=1.0,\n    opacity_hover=1.0,\n)\n\n# Create XY chart\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"kagi-basic · pygal · anyplot.ai\",\n    x_title=\"Kagi Line Index\",\n    y_title=\"Price ($)\",\n    show_dots=False,\n    show_x_guides=False,\n    show_y_guides=True,\n    show_legend=False,\n    stroke=True,\n    fill=False,\n    margin=100,\n)\n\n# Combine all yang segments into one series\nyang_points = []\nfor seg in yang_segments:\n    yang_points.extend(seg)\n    yang_points.append((None, None))  # Break between segments\n\n# Combine all yin segments into one series\nyin_points = []\nfor seg in yin_segments:\n    yin_points.extend(seg)\n    yin_points.append((None, None))  # Break between segments\n\n# Add as two series\nchart.add(\"Yang (Bullish)\", yang_points, stroke_style={\"width\": YANG_WIDTH, \"linecap\": \"round\"})\nchart.add(\"Yin (Bearish)\", yin_points, stroke_style={\"width\": YIN_WIDTH, \"linecap\": \"round\"})\n\n# Render to SVG and customize\nsvg_data = chart.render()\nsvg_str = svg_data.decode(\"utf-8\")\n\n# CSS override to enforce stroke widths\ncss_override = f\"\"\"\n.serie-0 .line {{ stroke-width: {YANG_WIDTH}px !important; stroke: {YANG_COLOR} !important; }}\n.serie-1 .line {{ stroke-width: {YIN_WIDTH}px !important; stroke: {YIN_COLOR} !important; }}\n\"\"\"\nsvg_str = svg_str.replace(\"</style>\", css_override + \"</style>\")\n\n# Manual legend showing line styles\nlegend_bg = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nlegend_svg = f\"\"\"\n<g class=\"manual-legend\" transform=\"translate(3200, 180)\">\n  <rect x=\"-20\" y=\"-30\" width=\"1450\" height=\"80\" fill=\"{legend_bg}\" fill-opacity=\"0.95\" rx=\"8\"/>\n  <line x1=\"0\" y1=\"0\" x2=\"80\" y2=\"0\" stroke=\"{YANG_COLOR}\" stroke-width=\"{YANG_WIDTH}\" stroke-linecap=\"round\"/>\n  <text x=\"100\" y=\"10\" font-size=\"36\" fill=\"{INK}\" font-family=\"Verdana, sans-serif\">Yang (Bullish) — Thick</text>\n  <line x1=\"700\" y1=\"0\" x2=\"780\" y2=\"0\" stroke=\"{YIN_COLOR}\" stroke-width=\"{YIN_WIDTH}\" stroke-linecap=\"round\"/>\n  <text x=\"800\" y=\"10\" font-size=\"36\" fill=\"{INK}\" font-family=\"Verdana, sans-serif\">Yin (Bearish) — Thin</text>\n</g>\n\"\"\"\n\nsvg_str = svg_str.replace(\"</svg>\", legend_svg + \"</svg>\")\n\n# Save to script directory\nscript_dir = os.path.dirname(os.path.abspath(__file__))\n\n# Convert to PNG with theme-suffixed filename\npng_path = os.path.join(script_dir, f\"plot-{THEME}.png\")\ncairosvg.svg2png(bytestring=svg_str.encode(\"utf-8\"), write_to=png_path)\n\n# Save HTML version with theme-suffixed filename\nhtml_path = os.path.join(script_dir, f\"plot-{THEME}.html\")\nchart.render_to_file(html_path)\n"}