{"id":293,"date":"2026-02-23T15:31:42","date_gmt":"2026-02-23T07:31:42","guid":{"rendered":"https:\/\/www.goingside.site\/?p=293"},"modified":"2026-02-23T15:31:42","modified_gmt":"2026-02-23T07:31:42","slug":"akshare%e6%95%b0%e6%8d%ae%e9%87%87%e9%9b%86%e7%ae%80%e5%8d%95%e5%88%86%e6%9e%90%e5%8a%9f%e8%83%bd","status":"publish","type":"post","link":"https:\/\/www.goingside.site\/?p=293","title":{"rendered":"AKShare\u6570\u636e\u91c7\u96c6\u7b80\u5355\u5206\u6790\u529f\u80fd"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-294\" src=\"https:\/\/www.goingside.site\/wp-content\/uploads\/2026\/02\/\u91d1\u878d\u5feb\u6790Pro-V5.2\u9884\u8b66\u4e0e\u540c\u6b65\u589e\u5f3a\u7248-300x200.png\" alt=\"\" width=\"300\" height=\"200\" srcset=\"https:\/\/www.goingside.site\/wp-content\/uploads\/2026\/02\/\u91d1\u878d\u5feb\u6790Pro-V5.2\u9884\u8b66\u4e0e\u540c\u6b65\u589e\u5f3a\u7248-300x200.png 300w, https:\/\/www.goingside.site\/wp-content\/uploads\/2026\/02\/\u91d1\u878d\u5feb\u6790Pro-V5.2\u9884\u8b66\u4e0e\u540c\u6b65\u589e\u5f3a\u7248-1024x682.png 1024w, https:\/\/www.goingside.site\/wp-content\/uploads\/2026\/02\/\u91d1\u878d\u5feb\u6790Pro-V5.2\u9884\u8b66\u4e0e\u540c\u6b65\u589e\u5f3a\u7248-768x511.png 768w, https:\/\/www.goingside.site\/wp-content\/uploads\/2026\/02\/\u91d1\u878d\u5feb\u6790Pro-V5.2\u9884\u8b66\u4e0e\u540c\u6b65\u589e\u5f3a\u7248.png 1398w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<ul>\n<li>\n<div>\n<pre>import os\r\nimport tkinter as tk\r\nfrom tkinter import ttk, messagebox\r\nimport akshare as ak\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom datetime import datetime, timedelta\r\nimport threading\r\nimport matplotlib.pyplot as plt\r\nfrom matplotlib.backends.backend_tkagg import FigureCanvasTkAgg\r\nfrom sqlalchemy import create_engine, text\r\nfrom matplotlib.font_manager import FontProperties\r\nfrom scipy.signal import argrelextrema\r\nimport time\r\n\r\n\r\n# ================= 1. \u5b57\u4f53\u4e0e\u73af\u5883\u914d\u7f6e =================\r\ndef init_sys_font():\r\n    # \u4f18\u5148\u4f7f\u7528\u5fae\u8f6f\u96c5\u9ed1\uff0c\u589e\u5f3a\u4e2d\u6587\u663e\u793a\u6548\u679c\r\n    for path in [r\"C:\\Windows\\Fonts\\msyh.ttc\", r\"C:\\Windows\\Fonts\\simhei.ttf\"]:\r\n        if os.path.exists(path):\r\n            return FontProperties(fname=path)\r\n    return None\r\n\r\n\r\nMY_FONT = init_sys_font()\r\nif MY_FONT:\r\n    plt.rcParams['font.sans-serif'] = [MY_FONT.get_name()]\r\n    plt.rcParams['axes.unicode_minus'] = False\r\n\r\n# \u5168\u5c40\u53d8\u91cf\r\nGLOBAL_NAME_MAP = {}\r\nGLOBAL_TYPE_MAP = {}\r\n\r\n# ================= 2. \u6570\u636e\u5e93\u914d\u7f6e =================\r\nDB_CONFIG = {\r\n    \"host\": \"\u6570\u636e\u5e93ip\", \"port\": 3306,\r\n    \"user\": \"\u7528\u6237\u540d\u79f0\", \"password\": \"\u6570\u636e\u5e93\u5bc6\u7801\", \"database\": \"\u6570\u636e\u5e93\u540d\u79f0\"\r\n}\r\nengine_url = f\"mysql+pymysql:\/\/{DB_CONFIG['user']}:{DB_CONFIG['password']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}\/{DB_CONFIG['database']}?charset=utf8mb4\"\r\n# \u914d\u7f6e\u8fde\u63a5\u6c60\u4ee5\u5e94\u5bf9\u8fdc\u7a0b\u6570\u636e\u5e93\u7684\u4e0d\u7a33\u5b9a\u6027\r\ndb_engine = create_engine(engine_url, pool_size=10, max_overflow=20, pool_recycle=3600, pool_pre_ping=True)\r\n\r\n\r\n# ================= 3. \u7b97\u6cd5\u6838\u5fc3\uff1a\u652f\u6491\u538b\u529b\u4e0e\u9884\u8b66 =================\r\n\r\ndef find_robust_levels(df, window=20, sensitivity=0.015):\r\n    \"\"\"\r\n    \u901a\u8fc7\u5c40\u90e8\u6781\u503c\u5e76\u8fdb\u884c\u805a\u7c7b\u5408\u5e76\uff0c\u8ba1\u7b97\u7a33\u5065\u7684\u652f\u6491\u538b\u529b\u4f4d\r\n    \"\"\"\r\n    if len(df) &lt; window * 2: return [], []\r\n    prices = df['\u6536\u76d8'].values\r\n\r\n    # \u83b7\u53d6\u5c40\u90e8\u6781\u5927\/\u6781\u5c0f\u503c\u7d22\u5f15\r\n    max_idx = argrelextrema(prices, np.greater, order=window)[0]\r\n    min_idx = argrelextrema(prices, np.less, order=window)[0]\r\n\r\n    raw_res = sorted(prices[max_idx])\r\n    raw_sup = sorted(prices[min_idx])\r\n\r\n    def cluster_levels(levels):\r\n        if not levels: return []\r\n        clusters = []\r\n        curr = levels[0]\r\n        for i in range(1, len(levels)):\r\n            # \u5982\u679c\u4e24\u4e2a\u4ef7\u4f4d\u95f4\u8ddd\u5c0f\u4e8e sensitivity (\u9ed8\u8ba41.5%)\uff0c\u5219\u8fdb\u884c\u5408\u5e76\r\n            if (levels[i] - curr) \/ curr &lt; sensitivity:\r\n                curr = (curr + levels[i]) \/ 2\r\n            else:\r\n                clusters.append(curr)\r\n                curr = levels[i]\r\n        clusters.append(curr)\r\n        return clusters\r\n\r\n    return cluster_levels(raw_res), cluster_levels(raw_sup)\r\n\r\n\r\ndef check_price_alert(name, current_p, res_lvls, sup_lvls):\r\n    \"\"\"\r\n    \u9884\u8b66\u903b\u8f91\uff1a\u68c0\u67e5\u4ef7\u683c\u662f\u5426\u5904\u4e8e\u5173\u952e\u7684\u6280\u672f\u4f4d\u9644\u8fd1\r\n    \"\"\"\r\n    alerts = []\r\n    # \u652f\u6491\u4f4d\u68c0\u67e5\r\n    for s in sup_lvls:\r\n        ratio = (current_p - s) \/ s\r\n        if 0 &lt; ratio &lt; 0.015:\r\n            alerts.append(f\"\u3010{name}\u3011\u63a5\u8fd1\u652f\u6491\u4f4d {s:.2f} (\u5173\u6ce8\u4f01\u7a33)\")\r\n        elif -0.01 &lt; ratio &lt;= 0:\r\n            alerts.append(f\"\u3010{name}\u3011\u8dcc\u7a7f\u652f\u6491 {s:.2f} (\u98ce\u9669\u9884\u8b66!)\")\r\n\r\n    # \u538b\u529b\u4f4d\u68c0\u67e5\r\n    for r in res_lvls:\r\n        ratio = (r - current_p) \/ current_p\r\n        if 0 &lt; ratio &lt; 0.015:\r\n            alerts.append(f\"\u3010{name}\u3011\u63a5\u8fd1\u538b\u529b\u4f4d {r:.2f} (\u8b66\u60d5\u56de\u843d)\")\r\n\r\n    return \" | \".join(alerts) if alerts else \"\u25cf \u4ef7\u683c\u76ee\u524d\u5904\u4e8e\u9707\u8361\u533a\u95f4\"\r\n\r\n\r\n# ================= 4. \u6570\u636e\u5904\u7406\u903b\u8f91 =================\r\n\r\ndef update_status(msg, color=\"#2c3e50\"):\r\n    root.after(0, lambda: status_label.config(text=msg, foreground=color))\r\n\r\n\r\ndef update_name_mapping_to_db():\r\n    \"\"\"\u8865\u56de\u7684\u540d\u79f0\u6620\u5c04\u540c\u6b65\u529f\u80fd\"\"\"\r\n    update_status(\"\u25cf \u6b63\u5728\u4ece\u5168\u5e02\u573a\u540c\u6b65\u540d\u79f0\u6620\u5c04...\", \"#3498db\")\r\n\r\n    def task():\r\n        try:\r\n            df_s = ak.stock_zh_a_spot_em()[['\u4ee3\u7801', '\u540d\u79f0']]\r\n            df_s.columns = ['sec_code', 'sec_name']\r\n            df_s['sec_type'] = 'STOCK'\r\n\r\n            df_e = ak.fund_etf_spot_em()[['\u4ee3\u7801', '\u540d\u79f0']]\r\n            df_e.columns = ['sec_code', 'sec_name']\r\n            df_e['sec_type'] = 'ETF'\r\n\r\n            full_map = pd.concat([df_s, df_e], ignore_index=True).drop_duplicates(subset=['sec_code'])\r\n            with db_engine.begin() as conn:\r\n                conn.execute(text(\"DELETE FROM name_mapping\"))\r\n                full_map.to_sql('name_mapping', conn, if_exists='append', index=False)\r\n\r\n            global GLOBAL_NAME_MAP\r\n            GLOBAL_NAME_MAP = dict(zip(full_map['sec_code'], full_map['sec_name']))\r\n            update_status(f\"\u25cf \u6210\u529f\u5237\u65b0 {len(GLOBAL_NAME_MAP)} \u6761\u8bc1\u5238\u540d\u79f0\", \"#2ecc71\")\r\n            messagebox.showinfo(\"\u540c\u6b65\u6210\u529f\", \"\u672c\u5730\u4ee3\u7801\u540d\u79f0\u6620\u5c04\u8868\u5df2\u66f4\u65b0\u3002\")\r\n        except Exception as e:\r\n            update_status(\"\u25cf \u540d\u79f0\u540c\u6b65\u5931\u8d25\", \"#e74c3c\")\r\n            messagebox.showerror(\"\u9519\u8bef\", f\"\u65e0\u6cd5\u8fde\u63a5API\u6216\u6570\u636e\u5e93: {e}\")\r\n\r\n    threading.Thread(target=task, daemon=True).start()\r\n\r\n\r\ndef sync_data(specific_code=None, is_batch=False):\r\n    code = specific_code if specific_code else entry_code.get().strip()\r\n    if len(code) != 6: return\r\n\r\n    if not is_batch:\r\n        btn_run.config(state=tk.DISABLED)\r\n        update_status(f\"\u25cf \u6b63\u5728\u83b7\u53d6 {code} \u5386\u53f2\u6570\u636e...\", \"#3498db\")\r\n\r\n    def task():\r\n        try:\r\n            is_etf = code.startswith(('51', '58', '15', '16', '56'))\r\n            name = GLOBAL_NAME_MAP.get(code, f\"\u4ee3\u7801_{code}\")\r\n            table = \"all_etf_data\" if is_etf else \"all_stock_data\"\r\n            code_col = \"etf_code\" if is_etf else \"stock_code\"\r\n            name_col = \"etf_name\" if is_etf else \"stock_name\"\r\n\r\n            with db_engine.connect() as conn:\r\n                last_d = conn.execute(text(f\"SELECT MAX(\u65e5\u671f) FROM {table} WHERE {code_col} = :c\"),\r\n                                      {\"c\": code}).scalar()\r\n\r\n            start_date = (last_d + timedelta(days=1)).strftime('%Y%m%d') if last_d else \"20220101\"\r\n            today_str = datetime.now().strftime('%Y%m%d')\r\n\r\n            if not last_d or last_d &lt; datetime.now().date():\r\n                df_new = ak.fund_etf_hist_em(symbol=code, start_date=start_date, end_date=today_str,\r\n                                             adjust=\"qfq\") if is_etf \\\r\n                    else ak.stock_zh_a_hist(symbol=code, start_date=start_date, end_date=today_str, adjust=\"qfq\")\r\n\r\n                if df_new is not None and not df_new.empty:\r\n                    df_new['\u65e5\u671f'] = pd.to_datetime(df_new['\u65e5\u671f']).dt.date\r\n                    df_new[code_col], df_new[name_col] = code, name\r\n                    with db_engine.begin() as conn:\r\n                        df_new.to_sql(table, conn, if_exists='append', index=False)\r\n\r\n            if not is_batch:\r\n                full_df = pd.read_sql(text(f\"SELECT * FROM {table} WHERE {code_col} = :c ORDER BY \u65e5\u671f\"), db_engine,\r\n                                      params={\"c\": code})\r\n                root.after(0, lambda: refresh_ui(full_df, name, code))\r\n        except Exception as e:\r\n            if not is_batch: update_status(f\"\u25cf \u9519\u8bef: {str(e)[:30]}\", \"#e74c3c\")\r\n        finally:\r\n            if not is_batch: root.after(0, lambda: btn_run.config(state=tk.NORMAL))\r\n\r\n    threading.Thread(target=task, daemon=True).start()\r\n\r\n\r\n# ================= 5. UI \u4e0e\u56fe\u8868\u7ed8\u5236 =================\r\n\r\ndef refresh_ui(df, name, code):\r\n    if df is None or df.empty: return\r\n    for i in tree.get_children(): tree.delete(i)\r\n\r\n    latest = df.iloc[-1]\r\n    curr_p = latest['\u6536\u76d8']\r\n\r\n    # \u7b97\u6cd5\u8ba1\u7b97\u4e0e\u9884\u8b66\u5224\u5b9a\r\n    res_lvls, sup_lvls = find_robust_levels(df)\r\n    alert_msg = check_price_alert(name, curr_p, res_lvls, sup_lvls)\r\n\r\n    # \u72b6\u6001\u680f\u989c\u8272\u8054\u52a8\r\n    status_color = \"#e67e22\" if \"\u5173\u6ce8\" in alert_msg else (\"#e74c3c\" if \"\u98ce\u9669\" in alert_msg else \"#2ecc71\")\r\n    update_status(alert_msg, status_color)\r\n\r\n    y_df = df.tail(250)\r\n    stats_label.config(\r\n        text=f\"\u3010{name}\u3011({code}) \u6700\u65b0:{curr_p} ({latest['\u6da8\u8dcc\u5e45']}%) | 250\u65e5\u9ad8:{y_df['\u6700\u9ad8'].max()} \u4f4e:{y_df['\u6700\u4f4e'].min()}\")\r\n\r\n    # \u63d2\u5165\u8868\u683c\u6570\u636e\r\n    for _, r in df.sort_values('\u65e5\u671f', ascending=False).head(40).iterrows():\r\n        tag = 'up' if r['\u6da8\u8dcc\u5e45'] &gt; 0 else ('down' if r['\u6da8\u8dcc\u5e45'] &lt; 0 else '')\r\n        tree.insert(\"\", \"end\", values=(r['\u65e5\u671f'], r['\u6536\u76d8'], r['\u6700\u9ad8'], r['\u6700\u4f4e'], f\"{r['\u6da8\u8dcc\u5e45']}%\"), tags=(tag,))\r\n\r\n    draw_chart(df, name, code, res_lvls, sup_lvls)\r\n\r\n\r\ndef draw_chart(df, name, code, res_lvls, sup_lvls):\r\n    for widget in chart_inner_frame.winfo_children(): widget.destroy()\r\n    plt.close('all')\r\n\r\n    df['\u65e5\u671f'] = pd.to_datetime(df['\u65e5\u671f'])\r\n    plot_df = df.tail(120).copy()\r\n    curr_p = df['\u6536\u76d8'].iloc[-1]\r\n\r\n    fig = plt.Figure(figsize=(10, 8), dpi=100, facecolor='#f8f9fa')\r\n    gs = fig.add_gridspec(3, 1, height_ratios=[3, 1, 1], hspace=0.15)\r\n    ax1, ax2, ax3 = fig.add_subplot(gs[0]), fig.add_subplot(gs[1]), fig.add_subplot(gs[2])\r\n\r\n    # \u4e3b\u56fe\u4ef7\u683c\u4e0e\u5747\u7ebf\r\n    ax1.plot(plot_df['\u65e5\u671f'], plot_df['\u6536\u76d8'], color='#2980b9', lw=2, label='\u4ef7\u683c', zorder=5)\r\n    for m, c in zip([5, 20, 60], ['#e67e22', '#27ae60', '#8e44ad']):\r\n        ma = df['\u6536\u76d8'].rolling(m).mean().reindex(plot_df.index)\r\n        ax1.plot(plot_df['\u65e5\u671f'], ma, color=c, lw=1, label=f'MA{m}', alpha=0.5)\r\n\r\n    # \u7ed8\u5236\u667a\u80fd\u652f\u6491\u538b\u529b\u7ebf (\u6807\u7b7e\u7f6e\u4e8e\u53f3\u4fa7\u9632\u6b62\u5e72\u6270)\r\n    active_res = [l for l in res_lvls if l &gt; curr_p][:2]\r\n    active_sup = [l for l in sup_lvls if l &lt; curr_p][-2:]\r\n\r\n    for l in active_res:\r\n        ax1.axhline(l, color='#c0392b', ls='--', lw=1.2, alpha=0.6)\r\n        ax1.text(plot_df['\u65e5\u671f'].iloc[-1], l, f\" \u538b:{l:.2f}\", color='#c0392b', fontproperties=MY_FONT, va='bottom',\r\n                 fontsize=9, fontweight='bold')\r\n    for l in active_sup:\r\n        ax1.axhline(l, color='#1e8449', ls='--', lw=1.2, alpha=0.6)\r\n        ax1.text(plot_df['\u65e5\u671f'].iloc[-1], l, f\" \u652f:{l:.2f}\", color='#1e8449', fontproperties=MY_FONT, va='top',\r\n                 fontsize=9, fontweight='bold')\r\n\r\n    ax1.legend(prop=MY_FONT, loc='upper left', frameon=False, ncol=4)\r\n    ax1.grid(True, linestyle=':', alpha=0.4)\r\n    ax1.set_title(f\"{name} ({code}) \u667a\u80fd\u8d8b\u52bf\u5206\u6790\", fontproperties=MY_FONT, fontsize=12)\r\n\r\n    # \u6210\u4ea4\u91cf\u4e0eMACD\u7701\u7565\u90e8\u5206\u4e0e\u539f\u7248\u4e00\u81f4\uff0c\u589e\u52a0\u89c6\u89c9\u534f\u8c03\u6027\r\n    v_colors = ['#e74c3c' if x &gt; 0 else '#27ae60' for x in plot_df['\u6da8\u8dcc\u5e45']]\r\n    ax2.bar(plot_df['\u65e5\u671f'], plot_df['\u6210\u4ea4\u91cf'], color=v_colors, alpha=0.7)\r\n\r\n    exp1 = plot_df['\u6536\u76d8'].ewm(span=12).mean()\r\n    exp2 = plot_df['\u6536\u76d8'].ewm(span=26).mean()\r\n    dif = exp1 - exp2\r\n    dea = dif.ewm(span=9).mean()\r\n    macd = (dif - dea) * 2\r\n    ax3.bar(plot_df['\u65e5\u671f'], macd, color=['#e74c3c' if v &gt;= 0 else '#27ae60' for v in macd], alpha=0.6)\r\n    ax3.plot(plot_df['\u65e5\u671f'], dif, color='blue', lw=0.8, label='DIF')\r\n    ax3.plot(plot_df['\u65e5\u671f'], dea, color='orange', lw=0.8, label='DEA')\r\n\r\n    for ax in [ax1, ax2]: ax.set_xticks([])\r\n    ax3.tick_params(axis='x', rotation=30, labelsize=8)\r\n\r\n    canvas = FigureCanvasTkAgg(fig, master=chart_inner_frame)\r\n    canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)\r\n\r\n\r\n# ================= 6. UI \u4e8b\u4ef6\u903b\u8f91 =================\r\n\r\ndef on_key_release(event):\r\n    if event.keysym in (\"Up\", \"Down\", \"Return\"): return\r\n    pattern = entry_code.get().strip()\r\n    suggestion_list.delete(0, tk.END)\r\n    if len(pattern) &gt;= 2:\r\n        matches = [f\"{c} | {n}\" for c, n in GLOBAL_NAME_MAP.items() if pattern in c or pattern in n]\r\n        if matches:\r\n            # \u4fee\u6b63\uff1a\u76f8\u5bf9\u4e8e entry_code \u7684\u7edd\u5bf9\u4f4d\u7f6e\u5b9a\u4f4d\u63d0\u793a\u6846\r\n            suggestion_list.place(x=entry_code.winfo_x(), y=entry_code.winfo_y() + entry_code.winfo_height() + 5,\r\n                                  width=220)\r\n            for m in matches[:10]: suggestion_list.insert(tk.END, m)\r\n        else:\r\n            suggestion_list.place_forget()\r\n    else:\r\n        suggestion_list.place_forget()\r\n\r\n\r\ndef on_item_select(event):\r\n    if not suggestion_list.curselection(): return\r\n    val = suggestion_list.get(suggestion_list.curselection())\r\n    entry_code.delete(0, tk.END)\r\n    entry_code.insert(0, val.split(' | ')[0])\r\n    suggestion_list.place_forget()\r\n    sync_data()\r\n\r\n\r\n# --- \u4e3b\u754c\u9762\u542f\u52a8 ---\r\nroot = tk.Tk()\r\nroot.title(\"\u91d1\u878d\u5feb\u6790 Pro v5.2 (\u9884\u8b66\u4e0e\u540c\u6b65\u589e\u5f3a\u7248)\")\r\nroot.geometry(\"1400x900\")\r\nroot.configure(bg='#f5f6fa')\r\n\r\n# \u9876\u90e8\u5de5\u5177\u680f\r\ntop = ttk.Frame(root, padding=10);\r\ntop.pack(fill=tk.X)\r\nttk.Label(top, text=\"\u8f93\u5165\u4ee3\u7801\/\u540d\u79f0:\").pack(side=tk.LEFT)\r\nentry_code = ttk.Entry(top, width=20, font=('Consolas', 11))\r\nentry_code.pack(side=tk.LEFT, padx=5)\r\nentry_code.bind('&lt;KeyRelease&gt;', on_key_release)\r\n\r\nbtn_run = ttk.Button(top, text=\"\u540c\u6b65\u5e76\u67e5\u770b\", command=sync_data);\r\nbtn_run.pack(side=tk.LEFT, padx=5)\r\nttk.Button(top, text=\"\u5168\u91cf\u540c\u6b65\u5e93\", command=lambda: sync_data()).pack(side=tk.LEFT, padx=5)\r\n# \u91cd\u65b0\u52a0\u5165\u7684\u5173\u952e\u6309\u94ae\r\nttk.Button(top, text=\"\u5237\u65b0\u540d\u79f0\u6620\u5c04\", command=update_name_mapping_to_db).pack(side=tk.LEFT, padx=5)\r\n\r\n# \u4e3b\u663e\u793a\u533a\u57df\r\ncontent = ttk.PanedWindow(root, orient=tk.HORIZONTAL);\r\ncontent.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)\r\ntree_frame = ttk.Frame(content)\r\ntree = ttk.Treeview(tree_frame, columns=('D', 'C', 'H', 'L', 'P'), show='headings')\r\nfor c, t in zip(('D', 'C', 'H', 'L', 'P'), ('\u65e5\u671f', '\u4ef7\u683c', '\u6700\u9ad8', '\u6700\u4f4e', '\u6da8\u8dcc')):\r\n    tree.heading(c, text=t);\r\n    tree.column(c, width=80, anchor='center')\r\ntree.tag_configure('up', foreground='#e74c3c');\r\ntree.tag_configure('down', foreground='#27ae60')\r\ntree.pack(fill=tk.BOTH, expand=True);\r\ncontent.add(tree_frame, weight=1)\r\n\r\nchart_container = ttk.Frame(content)\r\nstats_label = ttk.Label(chart_container, text=\"\u7cfb\u7edf\u51c6\u5907\u5c31\u7eea\", font=('\u5fae\u8f6f\u96c5\u9ed1', 11, 'bold'));\r\nstats_label.pack(pady=5)\r\nchart_inner_frame = ttk.Frame(chart_container);\r\nchart_inner_frame.pack(fill=tk.BOTH, expand=True)\r\ncontent.add(chart_container, weight=4)\r\n\r\n# \u72b6\u6001\u680f\r\nstatus_label = ttk.Label(root, text=\"\u25cf \u7cfb\u7edf\u5c31\u7eea\", font=('\u5fae\u8f6f\u96c5\u9ed1', 10, 'bold'), padding=(10, 5))\r\nstatus_label.pack(side=tk.BOTTOM, anchor=tk.W)\r\n\r\n# \u5efa\u8bae\u5217\u8868\r\nsuggestion_list = tk.Listbox(root, height=8, font=('\u5fae\u8f6f\u96c5\u9ed1', 10), bd=1, relief=\"solid\")\r\nsuggestion_list.bind('&lt;&lt;ListboxSelect&gt;&gt;', on_item_select)\r\n\r\n\r\n# \u521d\u59cb\u5316\u5f02\u6b65\u52a0\u8f7d\u6570\u636e\u5e93\u540d\u5355\r\ndef init_all():\r\n    try:\r\n        df = pd.read_sql(\"SELECT sec_code, sec_name FROM name_mapping\", db_engine)\r\n        global GLOBAL_NAME_MAP\r\n        GLOBAL_NAME_MAP = dict(zip(df['sec_code'], df['sec_name']))\r\n        update_status(f\"\u25cf \u672c\u5730\u5df2\u52a0\u8f7d {len(GLOBAL_NAME_MAP)} \u53ea\u8bc1\u5238\u4fe1\u606f\", \"#2ecc71\")\r\n    except:\r\n        update_status(\"\u25cf \u521d\u6b21\u8fd0\u884c\u8bf7\u70b9\u51fb [\u5237\u65b0\u540d\u79f0\u6620\u5c04]\", \"#f39c12\")\r\n\r\n\r\nthreading.Thread(target=init_all, daemon=True).start()\r\nroot.mainloop()<\/pre>\n<\/div>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>import os import tkinter as tk from tkinter import ttk, messagebox import akshare as ak import pandas as pd import numpy [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":294,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-293","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ruanjianfx"],"_links":{"self":[{"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/posts\/293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.goingside.site\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=293"}],"version-history":[{"count":1,"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/posts\/293\/revisions"}],"predecessor-version":[{"id":295,"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/posts\/293\/revisions\/295"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.goingside.site\/index.php?rest_route=\/wp\/v2\/media\/294"}],"wp:attachment":[{"href":"https:\/\/www.goingside.site\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.goingside.site\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.goingside.site\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}