{"spec_id":"kagi-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nkagi-basic: Basic Kagi Chart\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nfrom pathlib import Path\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\n\n\n# Get script directory for saving outputs\nSCRIPT_DIR = Path(__file__).parent\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\"\n\n# Okabe-Ito palette\nYANG_COLOR = \"#009E73\"  # Bluish green (brand)\nYIN_COLOR = \"#AE3030\"  # imprint red — bearish\n\n# Generate realistic stock price data (simulated random walk)\nnp.random.seed(42)\nn_days = 250\ndates = pd.date_range(\"2024-01-01\", periods=n_days, freq=\"D\")\nreturns = np.random.normal(0.0005, 0.02, n_days)\nprices = 100 * np.cumprod(1 + returns)\n\n# Kagi chart algorithm\n# Reversal threshold: 4% as specified\nreversal_pct = 0.04\n\n# Build Kagi line segments\nsegments = []\ncurrent_direction = None  # 1 = up, -1 = down\ncurrent_high = prices[0]\ncurrent_low = prices[0]\ncurrent_x = 0\nlast_y = prices[0]\n\n# Track yang/yin state (yang = thick/bullish, yin = thin/bearish)\nyang = True  # Start as yang\nlast_shoulder = prices[0]  # Last yang high\nlast_waist = prices[0]  # Last yin low\n\nfor i in range(1, len(prices)):\n    price = prices[i]\n\n    if current_direction is None:\n        # Initialize direction\n        if price >= current_high * (1 + reversal_pct):\n            current_direction = 1\n            segments.append({\"x\": current_x, \"y\": last_y, \"x2\": current_x, \"y2\": price, \"yang\": yang})\n            current_high = price\n            last_y = price\n        elif price <= current_low * (1 - reversal_pct):\n            current_direction = -1\n            segments.append({\"x\": current_x, \"y\": last_y, \"x2\": current_x, \"y2\": price, \"yang\": yang})\n            current_low = price\n            last_y = price\n    elif current_direction == 1:  # Currently going up\n        if price > current_high:\n            # Extend upward - update last segment\n            if segments:\n                segments[-1][\"y2\"] = price\n            current_high = price\n            last_y = price\n        elif price <= current_high * (1 - reversal_pct):\n            # Reversal down\n            # Check if we break below last waist (transition to yin)\n            if price < last_waist:\n                yang = False\n                last_waist = price\n            # Draw horizontal connector and vertical down\n            current_x += 1\n            segments.append({\"x\": current_x - 1, \"y\": last_y, \"x2\": current_x, \"y2\": last_y, \"yang\": yang})\n            last_shoulder = last_y  # Record shoulder\n            segments.append({\"x\": current_x, \"y\": last_y, \"x2\": current_x, \"y2\": price, \"yang\": yang})\n            current_low = price\n            current_direction = -1\n            last_y = price\n    else:  # current_direction == -1, going down\n        if price < current_low:\n            # Extend downward\n            if segments:\n                segments[-1][\"y2\"] = price\n            current_low = price\n            last_y = price\n        elif price >= current_low * (1 + reversal_pct):\n            # Reversal up\n            # Check if we break above last shoulder (transition to yang)\n            if price > last_shoulder:\n                yang = True\n                last_shoulder = price\n            # Draw horizontal connector and vertical up\n            current_x += 1\n            segments.append({\"x\": current_x - 1, \"y\": last_y, \"x2\": current_x, \"y2\": last_y, \"yang\": yang})\n            last_waist = last_y  # Record waist\n            segments.append({\"x\": current_x, \"y\": last_y, \"x2\": current_x, \"y2\": price, \"yang\": yang})\n            current_high = price\n            current_direction = 1\n            last_y = price\n\n# Create DataFrame for segments\ndf_segments = pd.DataFrame(segments)\ndf_segments[\"color\"] = df_segments[\"yang\"].map({True: YANG_COLOR, False: YIN_COLOR})\ndf_segments[\"thickness\"] = df_segments[\"yang\"].map({True: 6, False: 2})\ndf_segments[\"type\"] = df_segments[\"yang\"].map({True: \"Yang (Bullish)\", False: \"Yin (Bearish)\"})\n\n# Create Altair chart using mark_rule for line segments\nchart = (\n    alt.Chart(df_segments)\n    .mark_rule()\n    .encode(\n        x=alt.X(\"x:Q\", title=\"Line Index\"),\n        x2=\"x2:Q\",\n        y=alt.Y(\"y:Q\", title=\"Price ($)\", scale=alt.Scale(zero=False)),\n        y2=\"y2:Q\",\n        color=alt.Color(\n            \"type:N\",\n            scale=alt.Scale(domain=[\"Yang (Bullish)\", \"Yin (Bearish)\"], range=[YANG_COLOR, YIN_COLOR]),\n            legend=alt.Legend(title=\"Trend\"),\n        ),\n        strokeWidth=alt.StrokeWidth(\"yang:N\", scale=alt.Scale(domain=[True, False], range=[6, 2]), legend=None),\n    )\n    .properties(width=1600, height=900, title=\"kagi-basic · altair · anyplot.ai\", background=PAGE_BG)\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT)\n    .configure_title(fontSize=28, anchor=\"middle\", color=INK)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        labelFontSize=18,\n        titleColor=INK,\n        titleFontSize=22,\n    )\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        labelFontSize=16,\n        titleColor=INK,\n        titleFontSize=18,\n    )\n)\n\n# Save as PNG and HTML with theme-suffixed filenames\nchart.save(str(SCRIPT_DIR / f\"plot-{THEME}.png\"), scale_factor=3.0)\nchart.save(str(SCRIPT_DIR / f\"plot-{THEME}.html\"))\n"}