{"spec_id":"kagi-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nkagi-basic: Basic Kagi Chart\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 97/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette: green for yang (bullish), vermillion for yin (bearish)\nYANG_COLOR = \"#009E73\"  # Okabe-Ito position 1 - green\nYIN_COLOR = \"#AE3030\"  # imprint red — bearish\n\n# Generate sample price data designed to demonstrate multiple yang/yin transitions\nnp.random.seed(42)\n\n# Create price series with clear swings that will break through shoulders and waists\nprices_list = [100.0]\n\n# Generate data with deliberate swings to create yang/yin transitions\n# Key: Price must break above previous shoulder for yang, below previous waist for yin\nsegments = [\n    # (target_price, volatility, n_steps) - target relative to previous end\n    (15, 0.4, 15),  # Up to ~115\n    (-20, 0.5, 18),  # Down to ~95 (below 100 waist -> YIN)\n    (10, 0.4, 12),  # Up to ~105\n    (-18, 0.5, 15),  # Down to ~87 (below 95 waist -> still YIN)\n    (35, 0.5, 20),  # Up to ~122 (above 115 shoulder -> YANG!)\n    (-15, 0.4, 12),  # Down to ~107\n    (-22, 0.5, 18),  # Down to ~85 (below 87 waist -> YIN!)\n    (25, 0.4, 15),  # Up to ~110\n    (20, 0.5, 15),  # Up to ~130 (above 122 shoulder -> YANG!)\n    (-18, 0.4, 12),  # Down to ~112\n    (-30, 0.5, 20),  # Down to ~82 (below 85 waist -> YIN!)\n    (35, 0.5, 18),  # Up to ~117\n    (25, 0.5, 15),  # Up to ~142 (above 130 shoulder -> YANG!)\n    (-20, 0.4, 12),  # Down to ~122\n    (-35, 0.5, 20),  # Down to ~87 (below 82 waist -> YIN!)\n    (40, 0.5, 18),  # Up to ~127\n    (25, 0.5, 15),  # Up to ~152 (above 142 shoulder -> YANG!)\n    (-22, 0.4, 15),  # Down to ~130\n    (-45, 0.5, 22),  # Down to ~85 (below 87 waist -> YIN!)\n    (30, 0.5, 15),  # Up to ~115\n    (20, 0.5, 12),  # Up to ~135\n]\n\nfor target_move, vol, n_steps in segments:\n    start = prices_list[-1]\n    # Generate smooth movement with noise\n    base_trend = np.linspace(0, target_move, n_steps)\n    noise = np.cumsum(np.random.normal(0, vol, n_steps))\n    noise = noise - noise[-1] * np.linspace(0, 1, n_steps)  # Trend back to target\n    segment_prices = start + base_trend + noise\n    prices_list.extend(segment_prices.tolist())\n\nprices = np.array(prices_list)\n\n# Kagi chart parameters - 3% reversal threshold\nreversal_pct = 0.03\n\n# Build Kagi chart data with yang/yin tracking at reversal points\nkagi_points = []  # List of (x, y, is_yang) tuples\ncurrent_direction = 1  # 1 = up, -1 = down\ncurrent_high = prices[0]\ncurrent_low = prices[0]\nline_index = 0\nis_yang = True\n\n# Track shoulders (local highs) and waists (local lows) for yang/yin transitions\nprev_shoulder = prices[0]  # Previous local high (reversal point from up to down)\nprev_waist = prices[0]  # Previous local low (reversal point from down to up)\n\n# Start with initial point\nkagi_points.append((0, prices[0], is_yang))\n\nfor price in prices[1:]:\n    if current_direction == 1:  # Currently moving up\n        if price > current_high:\n            # Continue uptrend - extend the line\n            current_high = price\n            # Check if we break above previous shoulder -> become yang\n            if price > prev_shoulder and not is_yang:\n                is_yang = True\n            # Update last point\n            kagi_points[-1] = (kagi_points[-1][0], price, is_yang)\n        elif price <= current_high * (1 - reversal_pct):\n            # Reversal down - record shoulder and add new descending line\n            prev_shoulder = current_high  # This becomes a shoulder\n            line_index += 1\n            # Add horizontal connector at same y (shoulder)\n            kagi_points.append((line_index, kagi_points[-1][1], is_yang))\n            # Add new descending point\n            kagi_points.append((line_index, price, is_yang))\n            current_direction = -1\n            current_low = price\n    else:  # Currently moving down\n        if price < current_low:\n            # Continue downtrend - extend the line\n            current_low = price\n            # Check if we break below previous waist -> become yin\n            if price < prev_waist and is_yang:\n                is_yang = False\n            # Update last point\n            kagi_points[-1] = (kagi_points[-1][0], price, is_yang)\n        elif price >= current_low * (1 + reversal_pct):\n            # Reversal up - record waist and add new ascending line\n            prev_waist = current_low  # This becomes a waist\n            line_index += 1\n            # Add horizontal connector at same y (waist)\n            kagi_points.append((line_index, kagi_points[-1][1], is_yang))\n            # Add new ascending point\n            kagi_points.append((line_index, price, is_yang))\n            current_direction = 1\n            current_high = price\n\n# Extract x, y coordinates and yang/yin states\nkagi_x = [p[0] for p in kagi_points]\nkagi_y = [p[1] for p in kagi_points]\nyang_yin = [p[2] for p in kagi_points]\n\n# Create figure\nfig = go.Figure()\n\n# Draw Kagi lines segment by segment\ni = 0\nwhile i < len(kagi_x) - 1:\n    x_seg = [kagi_x[i], kagi_x[i + 1]]\n    y_seg = [kagi_y[i], kagi_y[i + 1]]\n\n    # Determine if this segment is yang or yin\n    is_yang_seg = yang_yin[i]\n\n    # Color and width based on yang/yin (per spec: green for yang, red for yin)\n    # Using 10/2 width ratio for strong visual differentiation\n    if is_yang_seg:\n        color = YANG_COLOR  # Green for yang (bullish)\n        width = 10\n    else:\n        color = YIN_COLOR  # Vermillion for yin (bearish)\n        width = 2\n\n    # Add line segment with hover information\n    trend_type = \"Yang (Bullish)\" if is_yang_seg else \"Yin (Bearish)\"\n    fig.add_trace(\n        go.Scatter(\n            x=x_seg,\n            y=y_seg,\n            mode=\"lines\",\n            line={\"color\": color, \"width\": width},\n            showlegend=False,\n            hovertemplate=f\"<b>{trend_type}</b><br>Price: ${'{y:.2f}'}<extra></extra>\",\n        )\n    )\n    i += 1\n\n# Mark reversal points (shoulders and waists) with small markers for clarity\n# Find horizontal segments (where x changes but y stays same)\nshoulder_x, shoulder_y = [], []\nwaist_x, waist_y = [], []\n\nfor i in range(len(kagi_x) - 2):\n    # Horizontal segment: x changes, y stays same\n    if kagi_x[i] != kagi_x[i + 1] and abs(kagi_y[i] - kagi_y[i + 1]) < 0.01:\n        # Look at next segment to determine if shoulder or waist\n        if i + 2 < len(kagi_y) and kagi_y[i + 2] < kagi_y[i + 1]:\n            # Price going down = shoulder (local high)\n            shoulder_x.append(kagi_x[i + 1])\n            shoulder_y.append(kagi_y[i + 1])\n        elif i + 2 < len(kagi_y) and kagi_y[i + 2] > kagi_y[i + 1]:\n            # Price going up = waist (local low)\n            waist_x.append(kagi_x[i + 1])\n            waist_y.append(kagi_y[i + 1])\n\n# Add shoulder markers (local highs where trend reverses down)\nif shoulder_x:\n    fig.add_trace(\n        go.Scatter(\n            x=shoulder_x,\n            y=shoulder_y,\n            mode=\"markers\",\n            marker={\"symbol\": \"triangle-down\", \"size\": 12, \"color\": YANG_COLOR, \"line\": {\"width\": 2, \"color\": PAGE_BG}},\n            name=\"Shoulder\",\n            hovertemplate=\"<b>Shoulder</b><br>Price: ${y:.2f}<extra></extra>\",\n        )\n    )\n\n# Add waist markers (local lows where trend reverses up)\nif waist_x:\n    fig.add_trace(\n        go.Scatter(\n            x=waist_x,\n            y=waist_y,\n            mode=\"markers\",\n            marker={\"symbol\": \"triangle-up\", \"size\": 12, \"color\": YIN_COLOR, \"line\": {\"width\": 2, \"color\": PAGE_BG}},\n            name=\"Waist\",\n            hovertemplate=\"<b>Waist</b><br>Price: ${y:.2f}<extra></extra>\",\n        )\n    )\n\n# Add legend entries with Okabe-Ito colors (matching widths)\nfig.add_trace(\n    go.Scatter(x=[None], y=[None], mode=\"lines\", line={\"color\": YANG_COLOR, \"width\": 10}, name=\"Yang (Bullish)\")\n)\nfig.add_trace(go.Scatter(x=[None], y=[None], mode=\"lines\", line={\"color\": YIN_COLOR, \"width\": 2}, name=\"Yin (Bearish)\"))\n\n# Layout with theme-adaptive styling\nfig.update_layout(\n    title={\n        \"text\": \"kagi-basic · plotly · anyplot.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Line Index\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"showgrid\": True,\n        \"gridwidth\": 1,\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Price ($)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"showgrid\": True,\n        \"gridwidth\": 1,\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    legend={\n        \"font\": {\"size\": 16, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"orientation\": \"h\",\n        \"yanchor\": \"bottom\",\n        \"y\": 1.01,\n        \"xanchor\": \"center\",\n        \"x\": 0.5,\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 100, \"b\": 80},\n    hovermode=\"x unified\",\n)\n\n# Save as PNG (4800x2700 px) and HTML\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}