<!DOCTYPE html>
<html lang="zh">
<head>
    <title>TradingView Chart with Python Terminal</title>
    <meta charset="utf-8" name="viewport" content=
            "width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">

    <script type="text/javascript" src=
            "{{url_for('charting_library',path='/charting_library.standalone.js')}}"></script>
    <link rel="stylesheet" href=
            "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">

    <style>
        /* 全局重置 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body, html {
            width: 100%;
            height: 100%;
            overflow: hidden;
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            background: #0b0e14;
        }

        .chart-area {
            width: 100%;
            height: 100%;
            background: #131821;
            overflow: hidden;
        }

        #tv_chart_container {
            width: 100%;
            height: 100%;
        }

        /* ----- Python 浮动面板样式 (主题自适应，修复深色文字可见性) ----- */
        .python-panel {
            position: fixed;
            z-index: 20000;
            width: 560px;
            max-width: 85vw;
            height: 640px;
            max-height: 80vh;
            border-radius: 24px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
            font-family: inherit;
        }

        .python-panel.dark-theme {
            background: rgba(18, 25, 35, 0.96);
            backdrop-filter: blur(24px) saturate(180%);
            border: 1px solid rgba(72, 120, 255, 0.5);
            box-shadow: 0 25px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.08);
            color: #e2e9ff; /* 基础文字颜色亮色 */
        }

        .python-panel.light-theme {
            background: rgba(245, 248, 250, 0.96);
            backdrop-filter: blur(24px) saturate(180%);
            border: 1px solid rgba(41, 98, 255, 0.3);
            box-shadow: 0 25px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.5);
            color: #1e293b;
        }

        .python-panel-header {
            padding: 14px 18px;
            border-bottom: 1px solid;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: grab;
            user-select: none;
        }

        .dark-theme .python-panel-header {
            background: rgba(12, 20, 30, 0.85);
            border-bottom-color: rgba(100, 150, 255, 0.35);
        }

        .light-theme .python-panel-header {
            background: rgba(240, 245, 250, 0.85);
            border-bottom-color: rgba(41, 98, 255, 0.2);
        }

        .panel-title {
            display: flex;
            align-items: center;
            gap: 10px;
            font-weight: 600;
            font-size: 15px;
        }

        .dark-theme .panel-title {
            background: linear-gradient(135deg, #a0c0ff, #6ea8fe);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
        }

        .light-theme .panel-title {
            background: linear-gradient(135deg, #1e4bd2, #3b82f6);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
        }

        .panel-title i {
            font-size: 18px;
            background: none;
            -webkit-background-clip: unset;
        }

        .dark-theme .panel-title i {
            color: #3b82f6;
        }

        .light-theme .panel-title i {
            color: #2962ff;
        }

        .panel-close {
            border-radius: 30px;
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s;
        }

        .dark-theme .panel-close {
            background: rgba(40, 55, 80, 0.8);
            color: #cbd5ff;
        }

        .light-theme .panel-close {
            background: rgba(220, 230, 245, 0.8);
            color: #2c3e66;
        }

        .panel-close:hover {
            background: #dc2626;
            color: white;
        }

        .terminal-code-area {
            flex: 1;
            padding: 16px;
            display: flex;
            flex-direction: column;
            min-height: 0;
        }

        .dark-theme .terminal-code-area {
            background: rgba(8, 14, 22, 0.5);
        }

        .light-theme .terminal-code-area {
            background: rgba(230, 240, 250, 0.5);
        }

        .terminal-prompt {
            margin-bottom: 6px;
            font-weight: 600;
        }

        .dark-theme .terminal-prompt {
            color: #6abf69;
        }

        .light-theme .terminal-prompt {
            color: #2b7e2e;
        }

        .terminal-input {
            width: 100%;
            flex: 1;
            border-radius: 16px;
            font-size: 13px;
            line-height: 1.5;
            resize: none;
            outline: none;
            padding: 14px;
            font-family: 'JetBrains Mono', monospace;
        }

        .dark-theme .terminal-input {
            background: rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(100, 160, 255, 0.4);
            color: #e2e9ff;
        }

        .dark-theme .terminal-input::placeholder {
            color: #8aa0c0;
        }

        .light-theme .terminal-input {
            background: white;
            border: 1px solid #cbd5e1;
            color: #1e293b;
        }

        .light-theme .terminal-input::placeholder {
            color: #94a3b8;
        }

        .terminal-input:focus {
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
        }

        .examples-bar {
            padding: 10px 16px;
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }

        .dark-theme .examples-bar {
            background: rgba(10, 18, 28, 0.7);
            border-top: 1px solid rgba(255, 255, 255, 0.08);
        }

        .light-theme .examples-bar {
            background: rgba(230, 240, 250, 0.6);
            border-top: 1px solid rgba(0, 0, 0, 0.05);
        }

        .example-chip {
            padding: 5px 14px;
            border-radius: 40px;
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            transition: 0.15s;
        }

        .dark-theme .example-chip {
            background: rgba(30, 45, 70, 0.8);
            border: 0.5px solid rgba(80, 140, 255, 0.5);
            color: #b4ccff;
        }

        .light-theme .example-chip {
            background: rgba(220, 230, 245, 0.9);
            border: 0.5px solid rgba(41, 98, 255, 0.3);
            color: #1e3a8a;
        }

        .example-chip:hover {
            background: #2e4a8e;
            color: white;
            transform: scale(1.02);
        }

        .terminal-output-area {
            height: 210px;
            overflow-y: auto;
            padding: 12px 14px;
            font-size: 12px;
        }

        .dark-theme .terminal-output-area {
            background: rgba(4, 10, 18, 0.75);
            backdrop-filter: blur(8px);
            border-top: 1px solid #2a3a55;
            color: #e2e9ff; /* 确保输出文字明亮 */
        }

        .light-theme .terminal-output-area {
            background: rgba(240, 248, 255, 0.8);
            border-top: 1px solid #d4dcec;
            color: #1e293b;
        }

        .terminal-output-area::-webkit-scrollbar {
            width: 5px;
        }

        .terminal-output-area::-webkit-scrollbar-track {
            background: transparent;
        }

        .terminal-output-area::-webkit-scrollbar-thumb {
            background: #3a506e;
            border-radius: 10px;
        }

        .output-line {
            margin: 6px 0;
            padding: 6px 12px;
            border-radius: 10px;
            border-left: 4px solid;
            white-space: pre-wrap;
            word-break: break-word;
        }

        .dark-theme .output-line {
            background: rgba(20, 30, 45, 0.5);
            color: inherit;
        }

        .light-theme .output-line {
            background: rgba(255, 255, 255, 0.7);
            color: inherit;
        }

        .output-line.success {
            border-left-color: #10b981;
        }

        .output-line.error {
            border-left-color: #f43f5e;
        }

        .output-line.info {
            border-left-color: #3b82f6;
        }

        .terminal-status-bar {
            padding: 8px 16px;
            font-size: 11px;
            display: flex;
            justify-content: space-between;
        }

        .dark-theme .terminal-status-bar {
            background: rgba(0, 0, 0, 0.4);
            border-top: 1px solid #2a3a55;
            color: #9ab3d0;
        }

        .light-theme .terminal-status-bar {
            background: rgba(230, 240, 250, 0.7);
            border-top: 1px solid #d1d9e6;
            color: #334155;
        }

        .status-dot {
            width: 9px;
            height: 9px;
            border-radius: 10px;
            display: inline-block;
            margin-right: 6px;
            box-shadow: 0 0 6px currentColor;
        }

        .status-dot.connected {
            background: #10b981;
            box-shadow: 0 0 12px #10b981;
            animation: pulse-glow 1.8s infinite;
        }

        .status-dot.disconnected {
            background: #f87171;
        }

        @keyframes pulse-glow {
            0% {
                opacity: 0.8;
                box-shadow: 0 0 5px #10b981;
            }
            100% {
                opacity: 1;
                box-shadow: 0 0 14px #34d399;
            }
        }

        .terminal-controls {
            display: flex;
            gap: 8px;
            pointer-events: auto;
        }

        .terminal-btn {
            border-radius: 30px;
            padding: 4px 14px;
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            transition: 0.2s;
        }

        .dark-theme .terminal-btn {
            background: rgba(40, 55, 80, 0.9);
            color: #d0e0ff;
            border: 0.5px solid rgba(100, 160, 255, 0.5);
        }

        .light-theme .terminal-btn {
            background: rgba(220, 230, 245, 0.9);
            color: #1e2c4a;
            border: 0.5px solid rgba(41, 98, 255, 0.3);
        }

        .terminal-btn:hover {
            background: rgba(59, 130, 246, 0.8);
            color: white;
            border-color: #3b82f6;
        }

        .terminal-btn.primary {
            background: linear-gradient(145deg, #1e4bd2, #3b82f6);
            border: none;
            color: white;
            box-shadow: 0 2px 8px #1e4bd280;
        }

        .terminal-btn.primary:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
    </style>
</head>
<body>
<div class="chart-area">
    <div id="tv_chart_container"></div>
</div>

<script type="text/javascript">
    // ============ 配置 ============
    class Config {
        static get debug() {
            const params = new URLSearchParams(window.location.search);
            return params.get('debug') === 'true';
        }

        static get theme() {
            const params = new URLSearchParams(window.location.search);
            return params.get('theme') || "light";
        }

        static get exchange() {
            const params = new URLSearchParams(window.location.search);
            return params.get('exchange') || 'bitstamp';
        }

        static get symbol() {
            const params = new URLSearchParams(window.location.search);
            return params.get('symbol') || 'btcusd';
        }

        static get interval() {
            const params = new URLSearchParams(window.location.search);
            return params.get('interval') || '1';
        }

        static get cover() {
            const dict = new Map([
                [60, "1"],
                [180, "3"],
                [300, "5"],
                [900, "15"],
                [1800, "30"],
                [3600, "1H"],
                [7200, "2H"],
                [14400, "4h"],
                [21600, "6H"],
                [43200, "12H"],
                [86400, "1D"],
                [259200, "3D"]

            ]);
            const params = new URLSearchParams(window.location.search);
            const val = params.get('step');
            const step = Number(val);
            const o = !val || isNaN(step) ? 60 : step;
            return dict.get(Number(o)) || '1';
        }

        static get EMA_LENGTH() {
            const params = new URLSearchParams(window.location.search);
            return Number(params.get('ema')) || 31;
        }

        static get step() {
            const params = new URLSearchParams(window.location.search);
            const val = params.get('step');
            const step = Number(val);
            return !val || isNaN(step) ? 60 : step;
        }

        static get limit() {
            const params = new URLSearchParams(window.location.search);
            const val = params.get('limit');
            const limit = Number(val);
            return !val || isNaN(limit) ? 1000 : limit;
        }

        static get generator() {
            const params = new URLSearchParams(window.location.search);
            return params.get('generator') || 'False';
        }

        static get points() {
            const params = new URLSearchParams(window.location.search);
            const points = params.get('points');
            if (!points) return [];
            return points.split(',').map(Number).filter(n => !isNaN(n));
        }

        static get nonAsciiParams() {
            const params = new URLSearchParams(window.location.search);
            const result = {};
            for (const [key, value] of params.entries()) {
                if (/[^\u0020-\u007E]/.test(key)) {
                    result[key] = value;
                }
            }
            return result;
        }

        static formatPeriod(seconds) {
            const map = {
                60: '1分钟', 180: '3分钟', 300: '5分钟', 900: '15分钟', 1800: '30分钟',
                3600: '1小时', 7200: '2小时', 14400: '4小时', 21600: '6小时',
                43200: '12小时', 86400: '日线', 259200: '3日线'
            };
            return map[seconds] || `${seconds}秒`;
        }

    }

    class Utils {
        static generateClientId() {
            return 'client_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
        }

        static log(message, data = null) {
            if (Config.debug) {
                console.log(message, data || '');
            }
        }

        static error(message, error = null) {
            console.error(message, error || '');
        }

        static info(message, error = null) {
            console.info(message, error || '');
        }
    }

    // ============ 统一 WebSocket 管理器 ============
    class UnifiedWebSocketManager {
        constructor() {
            this.clientId = Utils.generateClientId();
            this.socket = null;
            this.isConnected = false;
            this.reconnectAttempts = 0;
            this.maxReconnectAttempts = 5;
            this.reconnectDelay = 3000;
            this.handlers = {chart: [], python: []};
        }

        connect() {
            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            this.socket = new WebSocket(`${protocol}//${window.location.host}/ws/${this.clientId}`);

            this.socket.onopen = () => {
                Utils.log("WebSocket统一连接已建立");
                this.isConnected = true;
                this.reconnectAttempts = 0;
                this.updateConnectionStatus(true);
            };

            this.socket.onclose = () => {
                Utils.log("WebSocket连接已关闭");
                this.isConnected = false;
                this.updateConnectionStatus(false);
                this.attemptReconnect();
            };

            this.socket.onerror = (error) => {
                Utils.error("WebSocket错误:", error);
                this.isConnected = false;
                this.updateConnectionStatus(false);
            };

            this.socket.onmessage = (event) => {
                try {
                    const message = JSON.parse(event.data);
                    this.handleMessage(message);
                } catch (error) {
                    Utils.error("解析WebSocket消息失败:", error);
                }
            };
        }

        handleMessage(message) {
            Utils.log("收到消息:", message);
            const module = message.module || 'chart';
            if (module === 'python') {
                this.handlePythonMessage(message);
            } else {
                this.handleChartMessage(message);
            }
        }

        handleChartMessage(message) {
            this.handlers.chart.forEach(handler => {
                try {
                    handler(message);
                } catch (error) {
                    Utils.error("图表消息处理错误:", error);
                }
            });
        }

        handlePythonMessage(message) {
            this.handlers.python.forEach(handler => {
                try {
                    handler(message);
                } catch (error) {
                    Utils.error("Python消息处理错误:", error);
                }
            });
        }

        sendChartMessage(message) {
            this.send({...message, module: 'chart'});
        }

        sendPythonMessage(command, data = {}) {
            this.send({command, ...data, module: 'python'});
        }

        send(message) {
            if (this.socket && this.socket.readyState === WebSocket.OPEN) {
                this.socket.send(JSON.stringify(message));
            } else {
                Utils.error("WebSocket未连接，消息未发送:", message);
            }
        }

        on(module, handler) {
            if (this.handlers[module]) {
                this.handlers[module].push(handler);
            }
        }

        off(module, handler) {
            if (this.handlers[module]) {
                this.handlers[module] = this.handlers[module].filter(h => h !== handler);
            }
        }

        updateConnectionStatus(connected) {
            const event = new CustomEvent('websocket-status', {detail: {connected}});
            window.dispatchEvent(event);
        }

        attemptReconnect() {
            if (this.reconnectAttempts < this.maxReconnectAttempts) {
                this.reconnectAttempts++;
                Utils.log(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
                setTimeout(() => this.connect(), this.reconnectDelay);
            } else {
                Utils.error("达到最大重连次数");
            }
        }

        disconnect() {
            if (this.socket) {
                this.socket.close();
            }
        }
    }

    // ============ Python 终端管理器 ============
    class PythonTerminalManager {
        constructor(websocketManager) {
            this.websocketManager = websocketManager;
            this.outputBuffer = [];
            this.init();
        }

        init() {
            this.bindEvents();
            this.websocketManager.on('python', (message) => {
                this.handlePythonMessage(message);
            });
            // 暴露接口供按钮调用
            window.pythonWidget = {
                executeCode: () => this.executeCode(),
                clearCode: () => this.clearCode(),
                loadExample: (name) => this.loadExample(name)
            };
        }

        bindEvents() {
            window.addEventListener('websocket-status', (event) => {
                this.updateConnectionStatus(event.detail.connected);
            });

            const input = document.getElementById('codeInput');
            input.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.key === 'Enter') {
                    e.preventDefault();
                    this.executeCode();
                }
            });
        }

        updateConnectionStatus(connected) {
            const dot = document.getElementById('statusDot');
            const text = document.getElementById('statusText');
            const info = document.getElementById('connectionInfo');
            if (connected) {
                dot.className = 'status-dot connected';
                text.textContent = '已连接';
                info.textContent = 'Python 3.x | 在线';
            } else {
                dot.className = 'status-dot';
                text.textContent = '离线';
                info.textContent = 'Python 3.x | 连接中...';
            }
        }

        executeCode() {
            const code = document.getElementById('codeInput').value.trim();
            if (!code) {
                this.appendOutput('❌ 错误: 请输入代码', 'error');
                return;
            }

            const executeBtn = document.getElementById('executeBtn');
            executeBtn.disabled = true;
            executeBtn.textContent = '执行中...';

            this.appendOutput(`[${new Date().toLocaleTimeString()}] 开始执行...`, 'info');
            this.websocketManager.sendPythonMessage('execute', {code});
        }

        clearCode() {
            document.getElementById('codeInput').value = '';
            this.appendOutput('代码已清空', 'info');
        }

        loadExample(name) {
            const examples = {
                'basic': `# 基础示例
print("Hello TradingView!")
import math
print(f"圆周率: {math.pi}")
print(f"2的平方根: {math.sqrt(2)}")
numbers = [1, 2, 3, 4, 5]
print(f"数字列表: {numbers}")
print(f"总和: {sum(numbers)}")`,
                'chart': `# 图表分析
print("=== 图表数据分析 ===")
# 这里可以访问观察员对象，例如：
# print(观察员.笔序列)`,
                'indicators': `# 技术指标计算
print("=== 技术指标 ===")`,
                'trend': `# 趋势分析
print("=== 趋势分析 ===")
序列 = 图表展示序列(观察员)
基础序列 = 观察员.笔序列[272:281]
扩展序列 = []
线段.扩展分析(None, 基础序列, 扩展序列,观察员,观察员.配置)
中枢.分析(扩展序列,序列,观察员,False,"临时扩展_")`
            };
            if (examples[name]) {
                document.getElementById('codeInput').value = examples[name];
                this.appendOutput(`已加载 ${name} 示例`, 'info');
            }
        }

        handlePythonMessage(message) {
            const executeBtn = document.getElementById('executeBtn');
            if (executeBtn) {
                executeBtn.disabled = false;
                executeBtn.textContent = '执行 (Ctrl+Enter)';
            }

            switch (message.type) {
                case 'execution_result':
                    if (message.success) {
                        if (message.output) {
                            this.appendOutput(message.output, 'success');
                        } else {
                            this.appendOutput('✅ 执行成功（无输出）', 'success');
                        }
                    } else {
                        let errorMsg = message.error?.message || message.message || '执行错误';
                        this.appendOutput(`❌ 执行失败: ${errorMsg}`, 'error');
                        if (message.output) {
                            this.appendOutput(message.output, 'error');
                        }
                    }
                    break;
                case 'environment_info':
                    this.appendOutput(`环境信息: ${message.execution_count} 次执行`, 'info');
                    break;
                case 'environment_reset':
                    this.appendOutput(message.message, 'info');
                    break;
                case 'chart_data':
                    if (message.data.error) {
                        this.appendOutput(`图表数据错误: ${message.data.error}`, 'error');
                    } else {
                        this.appendOutput(`图表数据已更新: ${message.data.symbol}`, 'info');
                    }
                    break;
            }
        }

        appendOutput(text, type = 'info') {
            const outputArea = document.getElementById('outputArea');
            const line = document.createElement('div');
            line.className = `output-line ${type}`;
            line.textContent = text;
            outputArea.appendChild(line);
            outputArea.scrollTop = outputArea.scrollHeight;

            const lines = outputArea.getElementsByClassName('output-line');
            if (lines.length > 100) {
                lines[0].remove();
            }
        }
    }

    // 在 TradingViewManager 类外部或内部定义映射表
    const SHAPE_MAP = {
        LineToolTrendLine: {shape: 'trend_line', minPoints: 2},
        LineToolRectangle: {shape: 'rectangle', minPoints: 2},
        LineToolArrowMarkDown: {shape: 'arrow_down', minPoints: 1},
        LineToolArrowMarkUp: {shape: 'arrow_up', minPoints: 1},
        LineToolPath: {shape: 'path', minPoints: 2},
        LineToolHorzLine: {shape: 'horizontal_line', minPoints: 1},
        LineToolVertLine: {shape: 'vertical_line', minPoints: 1},
        // 可根据需要添加更多...
    };

    // ============ TradingView 图表管理器 ============
    class TradingViewManager {
        constructor(websocketManager) {
            this.websocketManager = websocketManager;
            this.widget = null;
            this.shapeIds = new Map();
            this.onRealtimeCallback = null;
            this.initDataFeed();
            this.websocketManager.on('chart', (message) => {
                this.handleChartMessage(message);
            });
            this._defaultPropertiesCache = {};  // { [toolName]: { key: value } }
        }

        initDataFeed() {
            this.datafeed = {
                onReady: (callback) => {
                    Utils.log("[Datafeed.onReady]");
                    setTimeout(() => callback({
                        supports_search: false,
                        supports_group_request: false,
                        supports_marks: false,
                        supports_timescale_marks: true,
                        supports_time: true,
                        supported_resolutions: [Config.cover,],
                    }));
                },
                searchSymbols: async (userInput, exchange, symbolType, onResultReadyCallback) => {
                    Utils.log("[Datafeed.searchSymbols]", userInput, exchange, symbolType);
                },
                resolveSymbol: async (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
                    Utils.log("[Datafeed.resolveSymbol]", symbolName);
                    const symbolInfo = {
                        exchange: Config.exchange,
                        ticker: Config.symbol,
                        name: Config.symbol,
                        description: Config.symbol,
                        type: "",
                        session: "24x7",
                        timezone: "Asia/Shanghai",
                        minmov: 1,
                        pricescale: 100,
                        visible_plots_set: "ohlcv",
                        has_no_volume: false,
                        has_weekly_and_monthly: false,
                        supported_resolutions: ["1", "3", "5", "15", "30", "1H", "2H", "4H", "6H", "12H", "1D", "3D"],
                        volume_precision: 1,
                        data_status: "streaming",
                        has_intraday: true,
                        has_daily: true,
                        has_ticks: true,
                        monthly_multipliers: [],
                        weekly_multipliers: [],
                    };
                    onSymbolResolvedCallback(symbolInfo);
                },
                getBars: async (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
                    const {from, to, firstDataRequest} = periodParams;
                    Utils.log("[Datafeed.getBars]", {from, to, firstDataRequest});
                    onHistoryCallback([], {noData: true});
                },
                subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
                    Utils.log("[Datafeed.subscribeBars] Subscriber UID:", subscriberUID);
                    this.onRealtimeCallback = onRealtimeCallback;
                },
                unsubscribeBars: (subscriberUID) => {
                    Utils.log("[Datafeed.unsubscribeBars] Subscriber UID:", subscriberUID);
                }
            };
        }

        initWidget() {
            this.widget = new TradingView.widget({
                symbol: Config.exchange + ":" + Config.symbol,
                interval: Config.cover,
                timezone: "Asia/Shanghai",
                fullscreen: true,
                container: "tv_chart_container",
                datafeed: this.datafeed,
                library_path: "charting_library/",
                locale: "zh",
                theme: Config.theme,
                debug: false,
                timeframe: "3D",
                user_id: "public_user_id",
                client_id: "yourserver.com",
                favorites: {
                    intervals: ["1", "3", "5"],
                    drawingTools: ["LineToolPath", "LineToolRectangle", "LineToolTrendLine"],
                },
                disabled_features: [
                    "use_localstorage_for_settings",
                    "header_symbol_search",
                    "header_undo_redo",
                    "header_screenshot",
                    "header_compare",
                    "header_chart_type",
                    "go_to_date",
                ],
                time_frames: [
                    {text: "3d", resolution: Config.cover, description: "3 Days"},
                    {text: "7d", resolution: Config.cover, description: "7 Days"},
                    {text: "1m", resolution: Config.cover, description: "一个月"},
                    {text: "64d", resolution: Config.cover, description: "64天"},
                ],
            });
            return this.widget;
        }

        setupChartEvents() {
            this.widget.onChartReady(() => {
                this.websocketManager.sendChartMessage({
                    type: 'ready',
                    exchange: Config.exchange,
                    symbol: Config.symbol,
                    freq: Config.step,
                    limit: Config.limit,
                    generator: Config.generator,
                    points: Config.points,
                    config: Config.nonAsciiParams
                });

                const chart = this.widget.activeChart();

                this.widget.subscribe("drawing_event", (sourceId, drawingEventType) => {
                    if (drawingEventType.indexOf("click") === 0) {
                        this.handleDrawingClick(sourceId);
                    }
                });

                // 存储最新的十字线位置
                let currentCrosshair = {time: null, price: null};

                // 订阅十字线移动事件，持续更新位置
                chart.crossHairMoved().subscribe(null, ({time, price}) => {
                    currentCrosshair = {time, price};
                });

                // 订阅全局鼠标松开事件（仅在点击时处理）
                this.widget.subscribe('mouse_up', (params) => {
                    if (currentCrosshair.time !== null) {
                        const timestamp = currentCrosshair.time; // Unix时间戳（秒）
                        const price = currentCrosshair.price;

                        console.log(`用户点击K线时间: ${timestamp}, 价格: ${price}`);

                        // 如果你有历史数据数组，可以匹配完整K线信息
                        // const bar = yourBarsData.find(b => b.time === timestamp);
                        // console.log('完整K线:', bar);
                    }
                });


            });
        }

        handleDrawingClick(sourceId) {
            try {
                const shape = this.widget.activeChart().getShapeById(sourceId);
                if (!shape) return;

                const properties = shape.getProperties();
                const points = shape.getPoints();
                const toolName = shape._source.toolname;

                Utils.info("Drawing clicked:", {toolName, properties, points});

                switch (toolName) {
                    case "LineToolTrendLine":
                        shape.setProperties({showLabel: !properties.showLabel});
                        //shape.setProperties({textcolor: invertAlphaToRgba(properties.textcolor)});
                        this.websocketManager.sendChartMessage({
                            type: "query_by_index",
                            data_type: properties.title,
                            index: properties.title
                        });
                        break;
                    case "LineToolRectangle":
                        shape.setProperties({showLabel: !properties.showLabel});
                        //shape.setProperties({textColor: invertAlphaToRgba(properties.textColor)});
                        this.websocketManager.sendChartMessage({
                            type: "query_by_index",
                            data_type: properties.title,
                            index: properties.title
                        });
                        break;
                    case "LineToolArrowMarkDown":
                    case "LineToolArrowMarkUp":
                        shape.setProperties({showLabel: !properties.showLabel});
                        //shape.setProperties({color: invertAlphaToRgba(properties.color)});
                        break;
                    case "LineToolPath":
                        this.websocketManager.sendChartMessage({
                            type: "save_path",
                            data: properties,
                            points: points,
                            index: properties.title
                        });
                        const prices = points.map(item => Math.round(item.price));
                        const textToCopy = prices.join(', ');
                        navigator.clipboard.writeText(textToCopy)
                                .then(() => Utils.log('数据已复制到剪切板:', textToCopy))
                                .catch(err => Utils.error('复制失败:', err));
                        break;
                }
            } catch (error) {
                Utils.error("Error handling drawing click:", error);
            }
        }

        handleChartMessage(message) {
            Utils.log("处理图表消息:", message.type);
            switch (message.type) {
                case 'realtime':
                    if (this.widget && this.onRealtimeCallback) {
                        const bar = {
                            time: message.timestamp*1000,
                            close: message.close,
                            open: message.open,
                            high: message.high,
                            low: message.low,
                            volume: message.volume,
                        };
                        this.onRealtimeCallback(bar);
                    }
                    break;
                case 'shape':
                    if (message.cmd === "APPEND") {
                        this.addShape(message.id, message.points, message.name, message.overrides);
                    } else if (message.cmd === "REMOVE") {
                        this.removeShape(message.id);
                    } else if (message.cmd === "MODIFY") {
                        this.updateShape(message.id, message.points, message.overrides);
                    }
                    break;
                case 'query_result':
                    Utils.log("查询结果:", message.data);
                    break;
                case 'ready_ack':
                    Utils.log("图表分析器启动确认:", message);
                    break;
                case 'connected':
                    Utils.log("连接成功:", message);
                    break;
            }
        }

        async addShape(id, points, name, properties) {
            const options = {
                shape: name,
                lock: false,
                disableSelection: false,
                disableSave: false,
                disableUndo: true,
                overrides: properties || {},
                //zOrder: 'top',
            };
            try {
                if (!this.widget || !this.widget.chart) {
                    return;
                }

                // 重复判断
                const existShapeId = this.shapeIds.get(id);
                if (existShapeId) {
                    // Utils.error("图形已存在", {id});
                    await this.updateShape(id, points, properties);
                    return;
                }

                // 创建图形
                const shapeId = await this.widget.chart().createMultipointShape(points, options);
                const shape = this.widget.chart().getShapeById(shapeId);

                if (!shape) {
                    Utils.error("添加形状失败：shape 为 null", {id, shapeId});
                    return;
                }

                // 设置属性
                //Utils.log("shapeId:", shape.getProperties());
                //shape.setProperties(properties);
                shape.bringToFront();

                // 保存映射
                this.shapeIds.set(id, shapeId);
                Utils.log("添加形状", {id, shapeId});

            } catch (error) {
                Utils.error("添加形状失败", error);
            }
        }

        async removeShape(id) {
            try {
                if (!this.widget || !this.widget.chart) return;

                const shapeId = this.shapeIds.get(id);
                if (!shapeId) {
                    Utils.log("图形不存在，无需删除", {id});
                    return;
                }

                const shape = this.widget.chart().getShapeById(shapeId);
                if (shape) {
                    await this.widget.chart().removeEntity(shapeId);
                    this.shapeIds.delete(id);
                }

                Utils.log("删除形状成功", {id});

            } catch (error) {
                Utils.error("删除形状失败", error);
            }
        }

        async updateShape(id, points, properties) {
            try {
                if (!this.widget || !this.widget.chart) return;

                const shapeId = this.shapeIds.get(id);
                if (!shapeId) {
                    Utils.error("更新失败：图形不存在", {id});
                    return;
                }

                const shape = this.widget.chart().getShapeById(shapeId);
                if (!shape) {
                    Utils.error("更新失败：未找到图形实例", {id, shapeId});
                    return;
                }

                // 更新点位
                if (points) {
                    await shape.setPoints(points);
                }

                // 更新配置与属性
                if (properties) {
                    shape.setProperties(properties);
                }

                shape.bringToFront();
                Utils.log("更新形状成功", {id});

            } catch (error) {
                Utils.error("更新形状失败", error);
            }
        }

        async hideShapesByType(targetType, shapeType) {
            try {
                if (!this.widget || !this.widget.chart) return;

                this.widget.activeChart().getAllShapes().forEach(({name, id}) => {
                    if (name === shapeType) {
                        const shape = this.widget.activeChart().getShapeById(id);
                        const properties = shape.getProperties();
                        const customId = properties.title;
                        try {
                            const [flag, period, type, index] = customId.split(':');
                            if (type.indexOf(targetType) !== -1) {
                                shape.setProperties({visible: !properties.visible});
                                // Utils.log(`已隐藏图形: ${customId}`);
                            }
                        } catch (error) {
                            Utils.log(`隐藏失败: ${customId}`);
                            Utils.error("批量隐藏图形失败", error);
                        }
                    }
                });
            } catch (error) {
                Utils.error("批量隐藏图形失败", error);
            }
        }


        async getDefaultsForTool(toolName, points) {
            if (this._defaultPropertiesCache[toolName]) {
                return this._defaultPropertiesCache[toolName];
            }

            const config = SHAPE_MAP[toolName];
            if (!config) {
                console.warn(`未映射的工具 ${toolName}，跳过默认属性获取`);
                this._defaultPropertiesCache[toolName] = {};
                return {};
            }

            const chart = this.widget.activeChart();
            let shapeId = null;
            this._fetchingDefaults = true;

            try {
                shapeId = await chart.createMultipointShape(points, {
                    shape: config.shape,
                    disableSave: true,
                    disableUndo: true,
                    overrides: {visible: false}
                });

                const shape = chart.getShapeById(shapeId);
                if (shape) {
                    this._defaultPropertiesCache[toolName] = {...shape.getProperties()};
                    chart.removeEntity(shapeId, {disableUndo: true}); // 无需等待删除完成
                }
            } catch (error) {
                console.error(`获取默认属性失败 (${toolName}):`, error);
                this._defaultPropertiesCache[toolName] = {};
            } finally {
                this._fetchingDefaults = false;
            }
            return this._defaultPropertiesCache[toolName];
        }

        async syncShapeOverrides() {
            if (!this.widget || !this.widget.activeChart()) return;

            const chart = this.widget.activeChart();
            const shapes = chart.getAllShapes();
            const payloads = [];

            for (const {name, id} of shapes) {
                const shape = chart.getShapeById(id);
                if (!shape) continue;

                const toolName = shape._source.toolname;
                // 获取（或从缓存）该工具的默认属性
                const defaultProps = await this.getDefaultsForTool(toolName, shape.getPoints());
                const currentProps = shape.getProperties();

                const overrides = {};
                for (const [key, value] of Object.entries(currentProps)) {
                    // 只对比已缓存的字段，忽略未记录的属性（如内部生成的特殊字段）
                    if (defaultProps.hasOwnProperty(key) && value !== defaultProps[key]) {
                        overrides[key] = value;
                    }
                }

                payloads.push({
                    shapeType: SHAPE_MAP[toolName].shape,
                    points: shape.getPoints().map(p => ({time: p.time, price: p.price})),
                    overrides
                });
            }

            if (payloads.length > 0) {
                this.websocketManager.sendChartMessage({
                    type: 'sync_shape_overrides',
                    data: payloads
                });
            }
        }

        createHeaderButtons() {
            const buttonConfigs = [
                {
                    text: "笔买卖点", title: "显示隐藏买卖点", handler: () => {
                        this.hideShapesByType("笔中枢", "arrow_down");
                        this.hideShapesByType("笔中枢", "arrow_up")
                    }
                },
                {
                    text: "段买卖点", title: "显示隐藏买卖点", handler: () => {
                        this.hideShapesByType("中枢段", "arrow_down");
                        this.hideShapesByType("中枢段", "arrow_up")
                    }
                },
                {
                    text: "特征序列",
                    title: "显示隐藏特征序列",
                    handler: () => this.hideShapesByType("特征", "trend_line")
                },
                {text: "📤 固化至本地", title: "发送图形差异属性到服务器", handler: () => this.syncShapeOverrides()}
            ];

            buttonConfigs.forEach(config => {
                this.createHeaderButton(config.text, config.title, config.handler);
            });

            this.createButton();


        }

        createHeaderButton(text, title, clickHandler) {
            const button = this.widget.createButton();
            button.setAttribute("title", title);
            button.textContent = text;
            button.addEventListener("click", clickHandler);
        }

        setShapesVisibleByPeriod(rawType, periodSeconds, visible) {
            if (!this.widget || !this.widget.activeChart()) return;
            const allShapes = this.widget.activeChart().getAllShapes();
            allShapes.forEach(({id}) => {
                const shape = this.widget.activeChart().getShapeById(id);
                if (!shape) return;
                const props = shape.getProperties();
                const title = props.title || '';
                const parts = title.split(':');
                if (parts.length < 3) return;
                const [, periodStr, typeFromTitle] = parts;
                const periodFromTitle = Number(periodStr);
                if (typeFromTitle === rawType && periodFromTitle === periodSeconds) {
                    shape.setProperties({visible});
                }
            });
        }

        isPeriodVisible(rawType, periodSeconds) {
            if (!this.widget || !this.widget.activeChart()) return true;
            const allShapes = this.widget.activeChart().getAllShapes();
            let hasAny = false;
            for (const {id} of allShapes) {
                const shape = this.widget.activeChart().getShapeById(id);
                if (!shape) continue;
                const props = shape.getProperties();
                const title = props.title || '';
                const parts = title.split(':');
                if (parts.length < 3) continue;
                const [, periodStr, typeFromTitle] = parts;
                const periodFromTitle = Number(periodStr);
                if (typeFromTitle === rawType && periodFromTitle === periodSeconds) {
                    hasAny = true;
                    if (props.visible === false) return false;
                }
            }
            return hasAny;
        }

        /**
         * 创建动态下拉菜单（按基础类型 + 周期分组）
         */
        setupCustomDropdown(tvWidget) {
            if (!tvWidget) return;

            // ---------- 辅助函数：获取当前主题相关样式 ----------
            const isDark = () => tvWidget.getTheme() === 'dark';
            const getThemeColor = (light, dark) => isDark() ? dark : light;

            // ---------- 1. 创建工具栏按钮 ----------
            const createButton = () => {
                const btn = tvWidget.createButton({align: 'right'});
                btn.innerHTML = `⚙️ 图表工具`;
                btn.style.cssText = `
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    height: 32px;
                    padding: 0 12px;
                    margin: 0 4px;
                    border-radius: 4px;
                    font-size: 14px;
                    font-weight: 500;
                    color: ${getThemeColor('#131722', '#d1d4dc')};
                    background: transparent;
                    border: none;
                    cursor: pointer;
                    transition: background 0.2s, color 0.2s;
                    font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif;
                    user-select: none;
                `;
                btn.addEventListener('mouseenter', () => {
                    btn.style.background = isDark() ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.05)';
                });
                btn.addEventListener('mouseleave', () => {
                    btn.style.background = 'transparent';
                });
                return btn;
            };

            // ---------- 2. 创建下拉面板 DOM ----------
            const createPanel = () => {
                const panel = document.createElement('div');
                panel.style.cssText = `
                    position: absolute;
                    z-index: 10000;
                    background: ${getThemeColor('#ffffff', '#1e2532')};
                    border-radius: 8px;
                    box-shadow: 0 6px 20px rgba(0,0,0,0.3);
                    padding: 8px 0;
                    min-width: 240px;
                    max-height: 70vh;
                    overflow-y: auto;
                    border: 1px solid ${getThemeColor('#d1d4dc', '#3a4a6a')};
                    color: ${getThemeColor('#131722', '#d1d9e6')};
                    font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, sans-serif;
                    display: none;
                    backdrop-filter: blur(4px);
                `;
                document.body.appendChild(panel);
                return panel;
            };

            // ---------- 3. 刷新菜单内容（核心逻辑）----------
            const refreshMenu = (panel, triggerBtn) => {
                panel.innerHTML = '';
                const chart = tvWidget.activeChart();
                if (!chart) return;

                // 收集图形数据：{ 基础类型: Set(周期) }
                const typePeriods = {};
                chart.getAllShapes().forEach(({id}) => {
                    const shape = chart.getShapeById(id);
                    if (!shape) return;
                    const props = shape.getProperties();
                    const title = props.title || '';
                    const parts = title.split(':');
                    if (parts.length < 3) return;
                    const period = Number(parts[1]);
                    const rawType = parts[2];
                    if (!typePeriods[rawType]) typePeriods[rawType] = new Set();
                    typePeriods[rawType].add(period);
                });

                if (Object.keys(typePeriods).length === 0) {
                    const emptyDiv = document.createElement('div');
                    emptyDiv.textContent = '暂无图形数据';
                    emptyDiv.style.padding = '12px 16px';
                    emptyDiv.style.opacity = '0.7';
                    panel.appendChild(emptyDiv);
                    return;
                }

                // 为每个基础类型创建可折叠分组
                Object.keys(typePeriods).sort().forEach(baseType => {
                    const section = document.createElement('div');
                    const header = document.createElement('div');
                    header.style.cssText = `
                        padding: 10px 16px;
                        cursor: pointer;
                        display: flex;
                        align-items: center;
                        justify-content: space-between;
                        user-select: none;
                        background: ${isDark() ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)'};
                        border-bottom: 1px solid rgba(128,128,128,0.2);
                    `;
                    header.innerHTML = `<span style="font-weight:600;">${baseType}</span><span class="toggle-arrow" style="transition: transform 0.2s;">▼</span>`;
                    const content = document.createElement('div');
                    content.style.display = 'block';

                    header.addEventListener('click', () => {
                        const arrow = header.querySelector('.toggle-arrow');
                        const isHidden = content.style.display === 'none';
                        content.style.display = isHidden ? 'block' : 'none';
                        arrow.style.transform = isHidden ? 'rotate(0deg)' : 'rotate(-90deg)';
                    });

                    const periods = Array.from(typePeriods[baseType]).sort((a, b) => a - b);
                    periods.forEach(period => {
                        const row = document.createElement('div');
                        row.style.cssText = `
                            padding: 6px 16px 6px 28px;
                            display: flex;
                            align-items: center;
                            cursor: pointer;
                            transition: background 0.15s;
                        `;
                        row.addEventListener('mouseenter', () => {
                            row.style.background = isDark() ? '#2a374b' : '#f0f3fa';
                        });
                        row.addEventListener('mouseleave', () => {
                            row.style.background = 'transparent';
                        });

                        const checkbox = document.createElement('input');
                        checkbox.type = 'checkbox';
                        checkbox.checked = this.isPeriodVisible(baseType, period);
                        checkbox.style.cssText = `
                            margin-right: 12px;
                            accent-color: #2962ff;
                            width: 16px;
                            height: 16px;
                            cursor: pointer;
                        `;

                        const label = document.createElement('span');
                        label.textContent = Config.formatPeriod(period);
                        label.style.flex = '1';

                        row.appendChild(checkbox);
                        row.appendChild(label);
                        row.addEventListener('click', (e) => {
                            if (e.target !== checkbox) checkbox.checked = !checkbox.checked;
                            this.setShapesVisibleByPeriod(baseType, period, checkbox.checked);
                        });
                        content.appendChild(row);
                    });

                    section.appendChild(header);
                    section.appendChild(content);
                    panel.appendChild(section);
                });

                // 底部全局控制按钮
                const footer = document.createElement('div');
                footer.style.cssText = `
                    border-top: 1px solid ${isDark() ? '#3a4a6a' : '#d1d4dc'};
                    padding: 8px 16px;
                    display: flex;
                    gap: 8px;
                `;
                const showAllBtn = document.createElement('button');
                showAllBtn.textContent = '全部显示';
                showAllBtn.style.cssText = `flex:1; padding: 4px; background: #2563eb; color:white; border:none; border-radius: 4px; cursor:pointer;`;
                showAllBtn.addEventListener('click', () => {
                    Object.keys(typePeriods).forEach(baseType => {
                        typePeriods[baseType].forEach(period => {
                            this.setShapesVisibleByPeriod(baseType, period, true);
                        });
                    });
                    refreshMenu(panel, triggerBtn); // 递归刷新以更新复选框状态
                });

                const hideAllBtn = document.createElement('button');
                hideAllBtn.textContent = '全部隐藏';
                hideAllBtn.style.cssText = `flex:1; padding: 4px; background: #ef4444; color:white; border:none; border-radius: 4px; cursor:pointer;`;
                hideAllBtn.addEventListener('click', () => {
                    Object.keys(typePeriods).forEach(baseType => {
                        typePeriods[baseType].forEach(period => {
                            this.setShapesVisibleByPeriod(baseType, period, false);
                        });
                    });
                    refreshMenu(panel, triggerBtn);
                });

                footer.appendChild(showAllBtn);
                footer.appendChild(hideAllBtn);
                panel.appendChild(footer);
            };

            // ---------- 4. 面板显示/隐藏及定位 ----------
            const showPanel = (panel, triggerBtn) => {
                refreshMenu(panel, triggerBtn);
                panel.style.display = 'block';
                const rect = triggerBtn.getBoundingClientRect();
                requestAnimationFrame(() => {
                    const panelWidth = panel.offsetWidth;
                    let left = rect.right - panelWidth;
                    if (left < 0) left = 5;
                    panel.style.top = `${rect.bottom + 4}px`;
                    panel.style.left = `${left}px`;
                });
            };

            const hidePanel = (panel) => {
                panel.style.display = 'none';
            };

            // ---------- 5. 组装并绑定事件 ----------
            const customBtn = createButton();
            const panelDiv = createPanel();
            let isPanelVisible = false;

            customBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                if (isPanelVisible) {
                    hidePanel(panelDiv);
                    isPanelVisible = false;
                } else {
                    showPanel(panelDiv, customBtn);
                    isPanelVisible = true;
                }
            });

            // 点击外部关闭面板
            document.addEventListener('click', (e) => {
                if (!panelDiv.contains(e.target) && e.target !== customBtn && isPanelVisible) {
                    hidePanel(panelDiv);
                    isPanelVisible = false;
                }
            });

            // 窗口大小变化时重新定位
            window.addEventListener('resize', () => {
                if (isPanelVisible) {
                    showPanel(panelDiv, customBtn);
                }
            });

            // 保存刷新函数供外部调用（可选）
            this.refreshDropdown = () => refreshMenu(panelDiv, customBtn);
        }

        // 创建 Python 终端按钮 + 浮动面板
        setupPythonTerminal(tvWidget) {
            if (!tvWidget) return;

            // 私有状态变量
            let panelDiv = null;
            let panelVisible = false;
            let triggerButton = null;
            let resizeHandler = null;
            let panelPythonHandler = null;

            // 获取当前主题 (实时)
            const getCurrentTheme = () => tvWidget.getTheme ? tvWidget.getTheme() : 'dark';

            // 应用主题样式到面板 (确保文字颜色正确)
            const applyThemeToPanel = () => {
                if (!panelDiv) return;
                const theme = getCurrentTheme();
                panelDiv.classList.remove('dark-theme', 'light-theme');
                panelDiv.classList.add(theme === 'dark' ? 'dark-theme' : 'light-theme');
                // 额外确保连接状态点样式
                const statusDot = panelDiv.querySelector('#panelStatusDot');
                if (statusDot && this.websocketManager.isConnected) {
                    statusDot.classList.add('connected');
                }
            };

            // 智能定位面板 (确保在可视区域内)
            const repositionPanel = () => {
                if (!panelDiv || !triggerButton) return;
                const btnRect = triggerButton.getBoundingClientRect();
                const wasVisible = panelDiv.style.display === 'flex';
                if (!wasVisible) {
                    panelDiv.style.visibility = 'hidden';
                    panelDiv.style.display = 'flex';
                }
                const panelWidth = panelDiv.offsetWidth;
                const panelHeight = panelDiv.offsetHeight;
                let left = btnRect.right - panelWidth;
                let top = btnRect.bottom + 8;
                if (left < 10) left = 10;
                if (left + panelWidth > window.innerWidth - 10)
                    left = window.innerWidth - panelWidth - 10;
                if (top + panelHeight > window.innerHeight - 10) {
                    top = btnRect.top - panelHeight - 8;
                    if (top < 10) top = 10;
                }
                panelDiv.style.left = `${left}px`;
                panelDiv.style.top = `${top}px`;
                if (!wasVisible) {
                    panelDiv.style.display = 'none';
                    panelDiv.style.visibility = '';
                }
            };

            // 显示面板
            const showPanel = () => {
                if (!panelDiv) return;
                applyThemeToPanel();      // 确保主题样式最新
                repositionPanel();        // 校准位置
                panelDiv.style.display = 'flex';
                panelVisible = true;

                // 点击外部关闭
                const outsideClickListener = (e) => {
                    if (!panelDiv.contains(e.target) && !triggerButton.contains(e.target)) {
                        hidePanel();
                        document.removeEventListener('click', outsideClickListener);
                    }
                };
                setTimeout(() => document.addEventListener('click', outsideClickListener), 50);

                resizeHandler = () => {
                    if (panelVisible) repositionPanel();
                };
                window.addEventListener('resize', resizeHandler);
            };

            // 隐藏面板
            const hidePanel = () => {
                if (panelDiv) {
                    panelDiv.style.display = 'none';
                    panelVisible = false;
                    if (resizeHandler) {
                        window.removeEventListener('resize', resizeHandler);
                        resizeHandler = null;
                    }
                }
            };

            // 创建面板 DOM 并绑定事件 (只执行一次)
            const createPanelDOM = () => {
                const panel = document.createElement('div');
                panel.className = 'python-panel';
                panel.style.display = 'none';
                panel.innerHTML = `
                            <div class="python-panel-header" id="pythonPanelHeader">
                                <div class="panel-title">
                                    <i class="fab fa-python"></i> Python 交互式终端
                                </div>
                                <div class="terminal-controls" style="gap: 8px;">
                                    <button class="terminal-btn" id="clearPanelCodeBtn">清空</button>
                                    <button class="terminal-btn primary" id="executePanelCodeBtn">执行 (Ctrl+Enter)</button>
                                    <div class="panel-close" id="closePythonPanelBtn"><i class="fas fa-times"></i></div>
                                </div>
                            </div>
                            <div class="terminal-code-area">
                                <div class="terminal-prompt">>>> Python 3.x 环境 | 支持动态执行</div>
                                <textarea class="terminal-input" id="pythonCodeTextarea" placeholder="# 输入 Python 代码，点击执行或按 Ctrl+Enter"></textarea>
                            </div>
                            <div class="examples-bar">
                                <span class="example-chip" data-python-example="basic">基础示例</span>
                                <span class="example-chip" data-python-example="chart">图表分析</span>
                                <span class="example-chip" data-python-example="indicators">技术指标</span>
                                <span class="example-chip" data-python-example="trend">趋势分析</span>
                            </div>
                            <div class="terminal-output-area" id="pythonOutputArea">
                                <div class="output-line info">✨ 就绪 — 点击按钮执行代码，支持 Ctrl+Enter</div>
                            </div>
                            <div class="terminal-status-bar">
                                <div><span class="status-dot" id="panelStatusDot"></span> <span id="panelStatusText">连接中...</span></div>
                                <div>Python 3.x | WebSocket</div>
                            </div>
                        `;
                document.body.appendChild(panel);
                panelDiv = panel;

                // 获取内部元素
                const textarea = panel.querySelector('#pythonCodeTextarea');
                const executeBtn = panel.querySelector('#executePanelCodeBtn');
                const clearBtn = panel.querySelector('#clearPanelCodeBtn');
                const closeBtn = panel.querySelector('#closePythonPanelBtn');
                const outputArea = panel.querySelector('#pythonOutputArea');
                const statusDot = panel.querySelector('#panelStatusDot');
                const statusText = panel.querySelector('#panelStatusText');

                // 辅助输出函数
                const appendOutput = (text, type = 'info') => {
                    const line = document.createElement('div');
                    line.className = `output-line ${type}`;
                    line.textContent = text;
                    outputArea.appendChild(line);
                    outputArea.scrollTop = outputArea.scrollHeight;
                    if (outputArea.children.length > 150) outputArea.removeChild(outputArea.children[0]);
                };

                // 执行代码
                const executeCode = () => {
                    const code = textarea.value.trim();
                    if (!code) {
                        appendOutput('❌ 请输入代码', 'error');
                        return;
                    }
                    executeBtn.disabled = true;
                    executeBtn.textContent = '执行中...';
                    appendOutput(`[${new Date().toLocaleTimeString()}] 执行代码...`, 'info');
                    this.websocketManager.sendPythonMessage('execute', {code});
                };

                executeBtn.addEventListener('click', executeCode);
                clearBtn.addEventListener('click', () => {
                    textarea.value = '';
                    appendOutput('✏️ 代码已清空', 'info');
                });
                closeBtn.addEventListener('click', () => hidePanel());
                textarea.addEventListener('keydown', (e) => {
                    if (e.ctrlKey && e.key === 'Enter') {
                        e.preventDefault();
                        executeCode();
                    }
                });

                // 示例代码
                const examples = {
                    basic: `# 基础测试
print("Hello TradingView 图表!")
import math
print(f"圆周率: {math.pi}")
numbers = [1,2,3,4,5]
print(f"列表总和: {sum(numbers)}")
print("Python 终端集成成功")`,
                    chart: `# 图表分析示例
print("=== 图表数据上下文 ===")
print(f"当前周期: ${Config.cover}")
print(f"品种: ${Config.symbol}")
print(f"EMA 长度: ${Config.EMA_LENGTH}")`,
                    indicators: `# 技术指标模拟`,
                    trend: `# 趋势分析`
                };
                panel.querySelectorAll('.example-chip').forEach(chip => {
                    const exName = chip.getAttribute('data-python-example');
                    chip.addEventListener('click', () => {
                        if (examples[exName]) {
                            textarea.value = examples[exName];
                            appendOutput(`📁 已加载示例: ${exName}`, 'info');
                        } else {
                            appendOutput(`示例 ${exName} 不存在`, 'warning');
                        }
                    });
                });

                // WebSocket 连接状态更新
                const updateStatus = (connected) => {
                    if (connected) {
                        statusDot.className = 'status-dot connected';
                        statusText.textContent = '已连接';
                    } else {
                        statusDot.className = 'status-dot disconnected';
                        statusText.textContent = '离线';
                    }
                };
                window.addEventListener('websocket-status', (e) => updateStatus(e.detail.connected));
                updateStatus(this.websocketManager.isConnected);

                // Python 执行结果处理
                panelPythonHandler = (msg) => {
                    if (executeBtn) {
                        executeBtn.disabled = false;
                        executeBtn.textContent = '执行 (Ctrl+Enter)';
                    }
                    if (msg.type === 'execution_result') {
                        if (msg.success) {
                            if (msg.output) appendOutput(msg.output, 'success');
                            else appendOutput('✅ 执行成功 (无输出)', 'success');
                        } else {
                            let errMsg = msg.error?.message || msg.message || '执行错误';
                            appendOutput(`❌ 执行失败: ${errMsg}`, 'error');
                            if (msg.output) appendOutput(msg.output, 'error');
                        }
                    } else if (msg.type === 'environment_info') {
                        appendOutput(`ℹ️ 环境信息: ${msg.execution_count} 次执行`, 'info');
                    } else if (msg.type === 'environment_reset') {
                        appendOutput(msg.message, 'info');
                    }
                };
                this.websocketManager.on('python', panelPythonHandler);

                // 拖拽功能
                const header = panel.querySelector('#pythonPanelHeader');
                let isDragging = false, dragStartX, dragStartY, startLeft, startTop;
                header.addEventListener('mousedown', (e) => {
                    if (e.target.closest('.terminal-controls') || e.target.closest('.panel-close')) return;
                    e.preventDefault();
                    isDragging = true;
                    dragStartX = e.clientX;
                    dragStartY = e.clientY;
                    const left = panel.style.left;
                    const top = panel.style.top;
                    startLeft = left ? parseInt(left) : panel.offsetLeft;
                    startTop = top ? parseInt(top) : panel.offsetTop;
                    document.body.style.userSelect = 'none';
                    header.style.cursor = 'grabbing';
                });
                window.addEventListener('mousemove', (e) => {
                    if (!isDragging) return;
                    let newLeft = startLeft + (e.clientX - dragStartX);
                    let newTop = startTop + (e.clientY - dragStartY);
                    newLeft = Math.min(window.innerWidth - panel.offsetWidth - 15, Math.max(15, newLeft));
                    newTop = Math.min(window.innerHeight - panel.offsetHeight - 15, Math.max(15, newTop));
                    panel.style.left = `${newLeft}px`;
                    panel.style.top = `${newTop}px`;
                });
                window.addEventListener('mouseup', () => {
                    if (isDragging) {
                        isDragging = false;
                        document.body.style.userSelect = '';
                        header.style.cursor = 'grab';
                    }
                });
            };

            // 创建工具栏按钮
            const createToolbarButton = () => {
                const btn = tvWidget.createButton({align: 'right'});
                btn.innerHTML = `<i class="fab fa-python" style="margin-right: 6px;"></i>Python 终端`;
                btn.style.cssText = `
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            height: 32px;
                            padding: 0 14px;
                            margin: 0 6px;
                            border-radius: 6px;
                            font-size: 13px;
                            font-weight: 500;
                            background: rgba(30, 60, 100, 0.85);
                            color: #e0edff;
                            border: 1px solid rgba(59, 130, 246, 0.6);
                            cursor: pointer;
                            transition: all 0.2s;
                            backdrop-filter: blur(4px);
                            z-index: 100;
                        `;
                btn.addEventListener('mouseenter', () => {
                    btn.style.background = 'rgba(59, 130, 246, 0.9)';
                    btn.style.color = 'white';
                });
                btn.addEventListener('mouseleave', () => {
                    btn.style.background = 'rgba(30, 60, 100, 0.85)';
                    btn.style.color = '#e0edff';
                });
                return btn;
            };

            // 最终初始化：创建按钮和面板
            if (!panelDiv) createPanelDOM();
            triggerButton = createToolbarButton();
            triggerButton.addEventListener('click', (e) => {
                e.stopPropagation();
                if (panelVisible) hidePanel();
                else showPanel();
            });
        }

        async createButton() {
            // 现在你可以安全地使用 customBtn 作为触发器
            this.setupCustomDropdown(this.widget);
            // Python 终端按钮（核心）
            this.setupPythonTerminal(this.widget);
        }


    }

    // ============ 主应用程序 ============
    class App {
        constructor() {
            this.websocketManager = new UnifiedWebSocketManager();
            this.tradingViewManager = null;
            this.pythonTerminalManager = null;
        }

        init() {
            this.websocketManager.connect();
            this.initTradingView();
        }

        initTradingView() {
            if (typeof TradingView === 'undefined') {
                console.error("TradingView库未加载");
                setTimeout(() => this.initTradingView(), 1000);
                return;
            }

            this.tradingViewManager = new TradingViewManager(this.websocketManager);
            window.tvWidget = this.tradingViewManager.initWidget();
            window.tvWidget.headerReady().then(() => {
                window.tvWidget.activeChart().createStudy("MACD", false, false, {
                    in_0: 13,
                    in_1: 31,
                    in_2: 11,
                    in_3: "close"
                });
                //window.tvWidget.activeChart().createStudy("Volume", true);

                window.tvWidget.activeChart().createStudy('Moving Average Exponential', false, false, {length: Config.EMA_LENGTH});
                this.tradingViewManager.createHeaderButtons();
                this.tradingViewManager.setupChartEvents();
            });
        }

    }

    // ============ 启动应用 ============
    window.addEventListener("DOMContentLoaded", () => {
        new App().init();
    });
</script>
</body>
</html>
