{"id":6522,"date":"2025-10-29T13:02:01","date_gmt":"2025-10-29T05:02:01","guid":{"rendered":"http:\/\/192.168.1.29\/?p=6522"},"modified":"2025-10-29T13:02:02","modified_gmt":"2025-10-29T05:02:02","slug":"%e6%9f%90%e5%8f%aa%e8%82%a1%e7%a5%a8%e7%9b%98%e6%95%b4%e5%88%86%e6%9e%90pytthon%e7%a8%8b%e5%ba%8f%e8%a7%a3%e6%9e%90","status":"publish","type":"post","link":"http:\/\/xc.ipyingshe.net:5347\/?p=6522","title":{"rendered":"\u67d0\u53ea\u80a1\u7968\u76d8\u6574\u5206\u6790pytthon\u7a0b\u5e8f\u89e3\u6790"},"content":{"rendered":"\n<p><strong>\u683c\u529b\u7535\u5668 000651<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">&nbsp;\u6839\u636e\u4f60\u63d0\u4f9b\u7684\u4fe1\u606f\u548c\u4ee3\u7801\u7247\u6bb5\uff0c\u8fd9\u4e2a\u7a0b\u5e8f\u662f\u4e00\u4e2a\u80a1\u7968\u76d8\u6574\u5206\u6790\u5de5\u5177\uff0c\u4e3b\u8981\u7528\u4e8e\u8bc6\u522b\u80a1\u7968\u4ef7\u683c\u7684\u76d8\u6574\u533a\u95f4\u5e76\u8fdb\u884c\u6280\u672f\u5206\u6790\u3002\u4e0b\u9762\u662f\u5bf9\u7a0b\u5e8f\u529f\u80fd\u7684\u8be6\u7ec6\u89e3\u91ca\uff1a \u7a0b\u5e8f\u6838\u5fc3\u529f\u80fd<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u80a1\u7968\u6570\u636e\u83b7\u53d6<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7a0b\u5e8f\u4f7f\u7528baostock\u5e93\u83b7\u53d6\u80a1\u7968\u7684\u5386\u53f2\u4ea4\u6613\u6570\u636e<\/li>\n\n\n\n<li>\u5305\u62ec\u5f00\u76d8\u4ef7\u3001\u6536\u76d8\u4ef7\u3001\u6700\u9ad8\u4ef7\u3001\u6700\u4f4e\u4ef7\u548c\u6210\u4ea4\u91cf\u7b49\u4fe1\u606f<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6280\u672f\u6307\u6807\u8ba1\u7b97<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u8ba1\u7b97\u5e03\u6797\u5e26\uff08Bollinger Bands\uff09\u6307\u6807\uff0c\u5305\u62ec\u4e0a\u8f68\u3001\u4e2d\u8f68\u548c\u4e0b\u8f68<\/li>\n\n\n\n<li>\u8ba1\u7b97\u76f8\u5bf9\u5f3a\u5f31\u6307\u6807\uff08RSI\uff09\uff0c\u7528\u4e8e\u8861\u91cf\u80a1\u7968\u7684\u8d85\u4e70\u8d85\u5356\u72b6\u6001<\/li>\n\n\n\n<li>\u8ba1\u7b97\u5e73\u5747\u771f\u5b9e\u6ce2\u52a8\u5e45\u5ea6\u767e\u5206\u6bd4\uff08ATR%\uff09\uff0c\u7528\u4e8e\u8861\u91cf\u4ef7\u683c\u6ce2\u52a8\u6027<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u76d8\u6574\u533a\u95f4\u8bc6\u522b<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7a0b\u5e8f\u80fd\u591f\u81ea\u52a8\u8bc6\u522b\u80a1\u7968\u4ef7\u683c\u7684\u76d8\u6574\u533a\u95f4<\/li>\n\n\n\n<li>\u76d8\u6574\u901a\u5e38\u8868\u73b0\u4e3a\u4ef7\u683c\u5728\u4e00\u5b9a\u8303\u56f4\u5185\u6ce2\u52a8\uff0c\u6ca1\u6709\u660e\u663e\u7684\u4e0a\u6da8\u6216\u4e0b\u8dcc\u8d8b\u52bf<\/li>\n\n\n\n<li>\u53ef\u80fd\u901a\u8fc7\u6bd4\u8f83\u4ef7\u683c\u4e0e\u5e03\u6797\u5e26\u7684\u5173\u7cfb\u3001RSI\u503c\u7684\u7a33\u5b9a\u6027\u7b49\u6765\u5224\u65ad<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6570\u636e\u53ef\u89c6\u5316<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4f7f\u7528matplotlib\u521b\u5efa\u56fe\u8868\u5c55\u793a\u5206\u6790\u7ed3\u679c<\/li>\n\n\n\n<li>\u5305\u542b\u81f3\u5c11\u4e24\u4e2a\u5b50\u56fe\uff1a\n<ul class=\"wp-block-list\">\n<li>\u7b2c\u4e00\u4e2a\u5b50\u56fe\u663e\u793a\u80a1\u7968\u4ef7\u683c\u548c\u5e03\u6797\u5e26\u6307\u6807<\/li>\n\n\n\n<li>\u7b2c\u4e8c\u4e2a\u5b50\u56fe\u663e\u793aRSI\u6307\u6807\u548cATR\u767e\u5206\u6bd4<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>\u7a0b\u5e8f\u4f1a\u5728\u56fe\u8868\u4e2d\u6807\u8bb0\u8bc6\u522b\u51fa\u7684\u76d8\u6574\u533a\u95f4<\/li>\n<\/ul>\n\n\n\n<p>\u6280\u672f\u6307\u6807\u89e3\u91ca<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u5e03\u6797\u5e26\uff08Bollinger Bands\uff09<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4e2d\u8f68\uff08middle_band\uff09\uff1a\u901a\u5e38\u662f20\u65e5\u79fb\u52a8\u5e73\u5747\u7ebf<\/li>\n\n\n\n<li>\u4e0a\u8f68\uff08upper_band\uff09\uff1a\u4e2d\u8f68\u52a0\u4e0a2\u500d\u6807\u51c6\u5dee<\/li>\n\n\n\n<li>\u4e0b\u8f68\uff08lower_band\uff09\uff1a\u4e2d\u8f68\u51cf\u53bb2\u500d\u6807\u51c6\u5dee<\/li>\n\n\n\n<li>\u5f53\u4ef7\u683c\u8d34\u8fd1\u4e0a\u8f68\u6216\u4e0b\u8f68\u65f6\uff0c\u53ef\u80fd\u8868\u793a\u8d85\u4e70\u6216\u8d85\u5356<\/li>\n\n\n\n<li>\u5f53\u5e03\u6797\u5e26\u6536\u7a84\u65f6\uff0c\u901a\u5e38\u9884\u793a\u7740\u5e02\u573a\u5373\u5c06\u51fa\u73b0\u5927\u7684\u6ce2\u52a8<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u76f8\u5bf9\u5f3a\u5f31\u6307\u6807\uff08RSI\uff09<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u53d6\u503c\u8303\u56f40-100\uff0c\u901a\u5e38\u4ee530\u548c70\u4f5c\u4e3a\u8d85\u5356\u548c\u8d85\u4e70\u7684\u754c\u9650<\/li>\n\n\n\n<li>RSI>70\u8868\u793a\u8d85\u4e70\uff0c\u53ef\u80fd\u9762\u4e34\u56de\u8c03<\/li>\n\n\n\n<li>RSI&lt;30\u8868\u793a\u8d85\u5356\uff0c\u53ef\u80fd\u5373\u5c06\u53cd\u5f39<\/li>\n\n\n\n<li>\u5728\u76d8\u6574\u533a\u95f4\u5185\uff0cRSI\u901a\u5e38\u572830-70\u4e4b\u95f4\u6ce2\u52a8<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u5e73\u5747\u771f\u5b9e\u6ce2\u52a8\u5e45\u5ea6\u767e\u5206\u6bd4\uff08ATR%\uff09<\/strong>\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u8861\u91cf\u4ef7\u683c\u6ce2\u52a8\u6027\u7684\u6307\u6807\uff0c\u4ee5\u767e\u5206\u6bd4\u5f62\u5f0f\u8868\u793a<\/li>\n\n\n\n<li>\u4f4eATR%\u503c\u901a\u5e38\u51fa\u73b0\u5728\u76d8\u6574\u533a\u95f4\uff0c\u8868\u793a\u6ce2\u52a8\u6027\u8f83\u5c0f<\/li>\n\n\n\n<li>\u9ad8ATR%\u503c\u901a\u5e38\u51fa\u73b0\u5728\u8d8b\u52bf\u660e\u663e\u7684\u5e02\u573a\uff0c\u8868\u793a\u6ce2\u52a8\u6027\u8f83\u5927<\/li>\n<\/ul>\n\n\n\n<p>\u7a0b\u5e8f\u7ed3\u6784\u63a8\u6d4b<\/p>\n\n\n\n<p>\u57fa\u4e8e\u5e38\u89c1\u7684Python\u6570\u636e\u5206\u6790\u7a0b\u5e8f\u7ed3\u6784\uff0c\u8be5\u7a0b\u5e8f\u53ef\u80fd\u5305\u542b\u4ee5\u4e0b\u51e0\u4e2a\u4e3b\u8981\u51fd\u6570\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6570\u636e\u83b7\u53d6\u51fd\u6570<\/strong>\uff1a\u8d1f\u8d23\u4ecebaostock\u83b7\u53d6\u80a1\u7968\u6570\u636e<\/li>\n\n\n\n<li><strong>\u6307\u6807\u8ba1\u7b97\u51fd\u6570<\/strong>\uff1a\u8ba1\u7b97\u5e03\u6797\u5e26\u3001RSI\u3001ATR\u7b49\u6280\u672f\u6307\u6807<\/li>\n\n\n\n<li><strong>\u76d8\u6574\u8bc6\u522b\u51fd\u6570<\/strong>\uff1a\u6839\u636e\u8bbe\u5b9a\u7684\u6761\u4ef6\u8bc6\u522b\u76d8\u6574\u533a\u95f4<\/li>\n\n\n\n<li><strong>\u53ef\u89c6\u5316\u51fd\u6570<\/strong>\uff1a\u521b\u5efa\u56fe\u8868\u5c55\u793a\u5206\u6790\u7ed3\u679c<\/li>\n\n\n\n<li><strong>\u4e3b\u51fd\u6570<\/strong>\uff1a\u534f\u8c03\u5404\u4e2a\u529f\u80fd\u6a21\u5757\u7684\u6267\u884c\u6d41\u7a0b<\/li>\n<\/ol>\n\n\n\n<p>\u5b9e\u9645\u5e94\u7528\u4ef7\u503c<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u4ea4\u6613\u51b3\u7b56\u8f85\u52a9<\/strong>\uff1a\u901a\u8fc7\u8bc6\u522b\u76d8\u6574\u533a\u95f4\uff0c\u53ef\u4ee5\u5e2e\u52a9\u4ea4\u6613\u8005\u5224\u65ad\u7a81\u7834\u65f6\u673a<\/li>\n\n\n\n<li><strong>\u98ce\u9669\u63a7\u5236<\/strong>\uff1a\u4e86\u89e3\u80a1\u7968\u7684\u6ce2\u52a8\u7279\u6027\uff0c\u6709\u52a9\u4e8e\u8bbe\u7f6e\u5408\u7406\u7684\u6b62\u635f\u548c\u6b62\u76c8\u4f4d<\/li>\n\n\n\n<li><strong>\u8d8b\u52bf\u5224\u65ad<\/strong>\uff1a\u7ed3\u5408\u591a\u4e2a\u6280\u672f\u6307\u6807\uff0c\u53ef\u4ee5\u66f4\u51c6\u786e\u5730\u5224\u65ad\u80a1\u7968\u7684\u672a\u6765\u8d70\u52bf<\/li>\n\n\n\n<li><strong>\u91cf\u5316\u5206\u6790\u57fa\u7840<\/strong>\uff1a\u4e3a\u8fdb\u4e00\u6b65\u7684\u91cf\u5316\u4ea4\u6613\u7b56\u7565\u5f00\u53d1\u63d0\u4f9b\u6280\u672f\u652f\u6301<\/li>\n<\/ol>\n\n\n\n<p>\u8fd9\u4e2a\u7a0b\u5e8f\u9002\u5408\u5bf9\u80a1\u7968\u6280\u672f\u5206\u6790\u611f\u5174\u8da3\u7684\u6295\u8d44\u8005\u548c\u4ea4\u6613\u8005\u4f7f\u7528\uff0c\u901a\u8fc7\u53ef\u89c6\u5316\u7684\u65b9\u5f0f\u76f4\u89c2\u5730\u5c55\u793a\u80a1\u7968\u7684\u76d8\u6574\u7279\u6027\u548c\u6280\u672f\u6307\u6807\u72b6\u6001\uff0c\u5e2e\u52a9\u505a\u51fa\u66f4\u660e\u667a\u7684\u6295\u8d44\u51b3\u7b56\u3002<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"585\" src=\"http:\/\/192.168.1.29\/wp-content\/uploads\/2025\/10\/\u683c\u529b\u7535\u5668\u76d8\u6574\u60c5\u51b5-1024x585.png\" alt=\"\" class=\"wp-image-6523\" srcset=\"http:\/\/xc.ipyingshe.net:5347\/wp-content\/uploads\/2025\/10\/\u683c\u529b\u7535\u5668\u76d8\u6574\u60c5\u51b5-1024x585.png 1024w, http:\/\/xc.ipyingshe.net:5347\/wp-content\/uploads\/2025\/10\/\u683c\u529b\u7535\u5668\u76d8\u6574\u60c5\u51b5-300x171.png 300w, http:\/\/xc.ipyingshe.net:5347\/wp-content\/uploads\/2025\/10\/\u683c\u529b\u7535\u5668\u76d8\u6574\u60c5\u51b5-768x439.png 768w, http:\/\/xc.ipyingshe.net:5347\/wp-content\/uploads\/2025\/10\/\u683c\u529b\u7535\u5668\u76d8\u6574\u60c5\u51b5.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><strong>python\u6e90\u4ee3\u7801<\/strong>\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport baostock as bs\nimport time\nimport matplotlib.font_manager as fm\n\n# \u83b7\u53d6\u80a1\u7968\u6570\u636e\ndef get_stock_data(stock_code, start_date, end_date):\n    # \u767b\u5f55baostock\n    lg = bs.login()\n    if lg.error_code != '0':\n        print(f\"\u767b\u5f55\u5931\u8d25: {lg.error_msg}\")\n        # \u5982\u679c\u767b\u5f55\u5931\u8d25\uff0c\u4f7f\u7528\u6a21\u62df\u6570\u636e\n        return generate_sample_data(start_date, end_date)\n    \n    # \u83b7\u53d6\u65e5\u7ebf\u6570\u636e\n    rs = bs.query_history_k_data_plus(\n        stock_code,\n        \"date,open,high,low,close,volume\",\n        start_date=start_date,\n        end_date=end_date,\n        frequency=\"d\",\n        adjustflag=\"3\"  # \u4e0d\u590d\u6743\n    )\n    \n    # \u5904\u7406\u6570\u636e\n    if rs.error_code != '0':\n        print(f\"\u83b7\u53d6\u6570\u636e\u5931\u8d25: {rs.error_msg}\")\n        bs.logout()\n        # \u5982\u679c\u83b7\u53d6\u6570\u636e\u5931\u8d25\uff0c\u4f7f\u7528\u6a21\u62df\u6570\u636e\n        return generate_sample_data(start_date, end_date)\n    \n    # \u8f6c\u5316\u4e3aDataFrame\n    data_list = &#91;]\n    while (rs.error_code == '0') &amp; rs.next():\n        data_list.append(rs.get_row_data())\n    \n    if not data_list:\n        print(\"\u672a\u83b7\u53d6\u5230\u6570\u636e\uff0c\u4f7f\u7528\u6a21\u62df\u6570\u636e\")\n        bs.logout()\n        return generate_sample_data(start_date, end_date)\n    \n    df = pd.DataFrame(data_list, columns=rs.fields)\n    \n    # \u6570\u636e\u7c7b\u578b\u8f6c\u6362\n    df&#91;'date'] = pd.to_datetime(df&#91;'date'])\n    df&#91;'open'] = df&#91;'open'].astype(float)\n    df&#91;'high'] = df&#91;'high'].astype(float)\n    df&#91;'low'] = df&#91;'low'].astype(float)\n    df&#91;'close'] = df&#91;'close'].astype(float)\n    df&#91;'volume'] = df&#91;'volume'].astype(float)\n    \n    # \u8bbe\u7f6e\u7d22\u5f15\u5e76\u6392\u5e8f\n    df = df.sort_values('date')\n    df.set_index('date', inplace=True)\n    \n    # \u767b\u51fabaostock\n    bs.logout()\n    \n    print(f\"\u6210\u529f\u83b7\u53d6{stock_code}\u7684{start_date}\u81f3{end_date}\u6570\u636e\uff0c\u5171{len(df)}\u6761\")\n    return df\n\n# \u751f\u6210\u6a21\u62df\u6570\u636e\uff08\u5f53\u65e0\u6cd5\u83b7\u53d6\u771f\u5b9e\u6570\u636e\u65f6\u4f7f\u7528\uff09\ndef generate_sample_data(start_date, end_date):\n    print(\"\u4f7f\u7528\u6a21\u62df\u6570\u636e\u8fdb\u884c\u6f14\u793a\")\n    dates = pd.date_range(start=start_date, end=end_date)\n    days = len(dates)\n    \n    # \u751f\u6210\u4e00\u4e2a\u6709\u8d8b\u52bf\u548c\u76d8\u6574\u7684\u4ef7\u683c\u5e8f\u5217\n    np.random.seed(42)\n    base_price = 100\n    trend = base_price + np.random.normal(0, 2, days).cumsum()\n    \n    # \u5728\u4e2d\u95f4\u90e8\u5206\u6dfb\u52a0\u76d8\u6574\u884c\u60c5\n    consolidation_start = int(days * 0.3)\n    consolidation_end = int(days * 0.7)\n    consolidation_mean = trend&#91;consolidation_start]\n    trend&#91;consolidation_start:consolidation_end] = consolidation_mean + np.random.normal(0, 1.5, consolidation_end-consolidation_start)\n    \n    df = pd.DataFrame({\n        'open': trend - np.random.uniform(0, 1, days),\n        'high': trend + np.random.uniform(0.5, 2, days),\n        'low': trend - np.random.uniform(0.5, 2, days),\n        'close': trend,\n        'volume': np.random.randint(10000, 100000, days)\n    }, index=dates)\n    \n    return df\n\n# \u624b\u52a8\u8ba1\u7b97\u5e03\u6797\u5e26\u5bbd\u5ea6\uff08\u4e0d\u4f7f\u7528TA-Lib\uff09\ndef calculate_bollinger_band_width(df, window=20):\n    # \u8ba1\u7b97\u79fb\u52a8\u5e73\u5747\u7ebf\n    df&#91;'middle_band'] = df&#91;'close'].rolling(window=window).mean()\n    # \u8ba1\u7b97\u6807\u51c6\u5dee\n    rolling_std = df&#91;'close'].rolling(window=window).std()\n    # \u8ba1\u7b97\u4e0a\u4e0b\u5e03\u6797\u5e26\n    df&#91;'upper_band'] = df&#91;'middle_band'] + (rolling_std * 2)\n    df&#91;'lower_band'] = df&#91;'middle_band'] - (rolling_std * 2)\n    # \u8ba1\u7b97\u5e03\u6797\u5e26\u5bbd\u5ea6\n    df&#91;'bb_width'] = (df&#91;'upper_band'] - df&#91;'lower_band']) \/ df&#91;'middle_band']\n    return df\n\n# \u624b\u52a8\u8ba1\u7b97RSI\uff08\u4e0d\u4f7f\u7528TA-Lib\uff09\ndef calculate_rsi(df, window=14):\n    delta = df&#91;'close'].diff()\n    gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()\n    loss = (-delta.where(delta &lt; 0, 0)).rolling(window=window).mean()\n    \n    # \u907f\u514d\u9664\u96f6\u9519\u8bef\n    rs = gain \/ loss.replace(0, np.nan)\n    df&#91;'rsi'] = 100 - (100 \/ (1 + rs))\n    return df\n\n# \u624b\u52a8\u8ba1\u7b97ATR\uff08\u4e0d\u4f7f\u7528TA-Lib\uff09\ndef calculate_atr(df, window=14):\n    # \u8ba1\u7b97\u6bcf\u65e5\u771f\u5b9e\u6ce2\u52a8\u5e45\u5ea6\n    df&#91;'tr1'] = df&#91;'high'] - df&#91;'low']\n    df&#91;'tr2'] = abs(df&#91;'high'] - df&#91;'close'].shift(1))\n    df&#91;'tr3'] = abs(df&#91;'low'] - df&#91;'close'].shift(1))\n    df&#91;'tr'] = df&#91;&#91;'tr1', 'tr2', 'tr3']].max(axis=1)\n    \n    # \u8ba1\u7b97ATR\uff08\u79fb\u52a8\u5e73\u5747\u771f\u5b9e\u6ce2\u52a8\u5e45\u5ea6\uff09\n    df&#91;'atr'] = df&#91;'tr'].rolling(window=window).mean()\n    \n    # \u6e05\u7406\u4e34\u65f6\u5217\n    df.drop(&#91;'tr1', 'tr2', 'tr3', 'tr'], axis=1, inplace=True)\n    \n    # \u8ba1\u7b97ATR\u767e\u5206\u6bd4\uff08\u76f8\u5bf9\u4e8e\u6536\u76d8\u4ef7\uff09\n    df&#91;'atr_pct'] = df&#91;'atr'] \/ df&#91;'close'] * 100\n    return df\n\n# \u5224\u65ad\u662f\u5426\u5904\u4e8e\u76d8\u6574\u72b6\u6001\ndef detect_consolidation(df, lookback_period=20):\n    # \u8ba1\u7b97\u5404\u79cd\u6280\u672f\u6307\u6807\n    df = calculate_bollinger_band_width(df)\n    df = calculate_rsi(df)\n    df = calculate_atr(df)\n    \n    # \u79fb\u9664NaN\u503c\n    df = df.dropna()\n    \n    if len(df) &lt; lookback_period:\n        print(f\"\u6570\u636e\u4e0d\u8db3{lookback_period}\u5929\uff0c\u65e0\u6cd5\u8fdb\u884c\u76d8\u6574\u5224\u65ad\")\n        return False, &#91;], {}\n    \n    # \u8ba1\u7b97\u6700\u8fd1\u4e00\u6bb5\u65f6\u95f4\u7684\u5e73\u5747\u6307\u6807\u503c\n    recent_df = df.tail(lookback_period)\n    avg_bb_width = recent_df&#91;'bb_width'].mean()\n    avg_rsi = recent_df&#91;'rsi'].mean()\n    avg_atr_pct = recent_df&#91;'atr_pct'].mean()\n    \n    # \u8ba1\u7b97\u4ef7\u683c\u53d8\u52a8\u8303\u56f4\n    price_range_pct = (recent_df&#91;'close'].max() - recent_df&#91;'close'].min()) \/ recent_df&#91;'close'].min() * 100\n    \n    # \u8bbe\u7f6e\u76d8\u6574\u5224\u65ad\u9608\u503c\n    bb_width_threshold = 0.05  # \u5e03\u6797\u5e26\u5bbd\u5ea6\u9608\u503c\n    rsi_threshold = 15  # RSI\u6ce2\u52a8\u8303\u56f4\u9608\u503c\n    atr_pct_threshold = 1.5  # ATR\u767e\u5206\u6bd4\u9608\u503c\n    price_range_threshold = 8  # \u4ef7\u683c\u53d8\u52a8\u8303\u56f4\u9608\u503c\n    \n    # \u5224\u65ad\u662f\u5426\u5904\u4e8e\u76d8\u6574\u72b6\u6001\n    is_consolidating = False\n    signals = &#91;]\n    \n    if avg_bb_width &lt; bb_width_threshold:\n        signals.append(\"\u5e03\u6797\u5e26\u6536\u7a84\")\n    if abs(avg_rsi - 50) &lt; rsi_threshold:\n        signals.append(\"RSI\u5728\u4e2d\u95f4\u533a\u57df\")\n    if avg_atr_pct &lt; atr_pct_threshold:\n        signals.append(\"\u6ce2\u52a8\u7387\u4f4e\")\n    if price_range_pct &lt; price_range_threshold:\n        signals.append(\"\u4ef7\u683c\u6ce2\u52a8\u8303\u56f4\u5c0f\")\n    \n    # \u5982\u679c\u6ee1\u8db3\u591a\u4e2a\u6761\u4ef6\uff0c\u5219\u5224\u65ad\u4e3a\u76d8\u6574\n    if len(signals) >= 3:\n        is_consolidating = True\n    \n    return is_consolidating, signals, {\n        'avg_bb_width': avg_bb_width,\n        'avg_rsi': avg_rsi,\n        'avg_atr_pct': avg_atr_pct,\n        'price_range_pct': price_range_pct\n    }\n\n# \u53ef\u89c6\u5316\u7ed3\u679c\n# \u5728\u5bfc\u5165matplotlib\u540e\u6dfb\u52a0\u7b80\u5355\u7684\u5b57\u4f53\u8bbe\u7f6e\nimport matplotlib.pyplot as plt\n# \u53ea\u4fdd\u7559\u6700\u57fa\u672c\u7684Windows\u4e2d\u6587\u5b57\u4f53\u8bbe\u7f6e\nplt.rcParams&#91;\"font.family\"] = &#91;\"SimHei\"]\nplt.rcParams&#91;\"axes.unicode_minus\"] = False  # \u89e3\u51b3\u8d1f\u53f7\u663e\u793a\u95ee\u9898\n\ndef plot_consolidation(df, consolidation_periods, stock_code):\n    plt.figure(figsize=(14, 8))\n    \n    # \u7ed8\u5236\u4ef7\u683c\u548c\u5e03\u6797\u5e26\n    plt.subplot(2, 1, 1)\n    plt.plot(df.index, df&#91;'close'], label='\u6536\u76d8\u4ef7')\n    plt.plot(df.index, df&#91;'upper_band'], 'r--', label='\u4e0a\u5e03\u6797\u5e26')\n    plt.plot(df.index, df&#91;'middle_band'], 'g--', label='\u4e2d\u5e03\u6797\u5e26')\n    plt.plot(df.index, df&#91;'lower_band'], 'r--', label='\u4e0b\u5e03\u6797\u5e26')\n    \n    # \u6807\u8bb0\u76d8\u6574\u533a\u95f4\n    for period in consolidation_periods:\n        if len(period) > 0:\n            plt.axvspan(period&#91;0], period&#91;-1], color='gray', alpha=0.3)\n    \n    plt.title(f'{stock_code} \u80a1\u7968\u4ef7\u683c\u4e0e\u5e03\u6797\u5e26')\n    plt.legend()\n    \n    # \u7ed8\u5236RSI\u548cATR\u767e\u5206\u6bd4\n    plt.subplot(2, 1, 2)\n    plt.plot(df.index, df&#91;'rsi'], label='RSI')\n    plt.axhline(70, color='r', linestyle='--')\n    plt.axhline(30, color='g', linestyle='--')\n    plt.axhline(50, color='b', linestyle='--', alpha=0.5)\n    plt.ylabel('RSI')\n    plt.legend(loc='upper left')\n    \n    plt.twinx()\n    plt.plot(df.index, df&#91;'atr_pct'], 'c-', label='ATR\u767e\u5206\u6bd4')\n    plt.ylabel('ATR\u767e\u5206\u6bd4 (%)', color='c')\n    plt.grid(True, alpha=0.3)\n    plt.title('RSI\u4e0eATR\u767e\u5206\u6bd4')\n    plt.legend(loc='upper right')\n    \n    plt.tight_layout()\n    plt.show()\n\n# \u4e3b\u51fd\u6570\ndef main():\n    # \u83b7\u53d6\u80a1\u7968\u6570\u636e\n    #stock_code = 'sh.600938'  # \u4e2d\u56fd\u6d77\u6cb9\n    stock_code = 'sz.000651' # \u683c\u529b\u7535\u5668\n    start_date = '2024-01-01'\n    end_date = '2025-12-31'\n    \n    df = get_stock_data(stock_code, start_date, end_date)\n    \n    # \u68c0\u6d4b\u6700\u8fd1\u4e00\u6bb5\u65f6\u95f4\u662f\u5426\u5904\u4e8e\u76d8\u6574\u72b6\u6001\n    lookback_period = 20\n    is_consolidating, signals, metrics = detect_consolidation(df, lookback_period)\n    \n    print(f\"\u80a1\u7968\u4ee3\u7801: {stock_code}\")\n    print(f\"\u5f53\u524d\u662f\u5426\u5904\u4e8e\u76d8\u6574\u72b6\u6001: {'\u662f' if is_consolidating else '\u5426'}\")\n    print(f\"\u76d8\u6574\u4fe1\u53f7: {', '.join(signals) if signals else '\u65e0'}\")\n    if metrics:\n        print(f\"\u6307\u6807\u503c: \u5e03\u6797\u5e26\u5bbd\u5ea6={metrics&#91;'avg_bb_width']:.4f}, RSI={metrics&#91;'avg_rsi']:.2f}\")\n        print(f\"        ATR\u767e\u5206\u6bd4={metrics&#91;'avg_atr_pct']:.2f}%, \u4ef7\u683c\u6ce2\u52a8\u8303\u56f4={metrics&#91;'price_range_pct']:.2f}%\")\n    \n    # \u68c0\u6d4b\u6574\u4e2a\u65f6\u95f4\u5e8f\u5217\u4e2d\u7684\u76d8\u6574\u533a\u95f4\n    window_size = 20\n    step = 5\n    consolidation_periods = &#91;]\n    \n    # \u5148\u9884\u5904\u7406\u6570\u636e\u4ee5\u907f\u514d\u91cd\u590d\u8ba1\u7b97\n    df = calculate_bollinger_band_width(df)\n    df = calculate_rsi(df)\n    df = calculate_atr(df)\n    df = df.dropna()\n    \n    if len(df) >= window_size:\n        for i in range(0, len(df) - window_size + 1, step):\n            window_df = df.iloc&#91;i:i+window_size]&#91;&#91;'close', 'high', 'low', 'bb_width', 'rsi', 'atr_pct']]\n            \n            # \u8ba1\u7b97\u7a97\u53e3\u5185\u7684\u6307\u6807\n            avg_bb_width = window_df&#91;'bb_width'].mean()\n            avg_rsi = window_df&#91;'rsi'].mean()\n            avg_atr_pct = window_df&#91;'atr_pct'].mean()\n            price_range_pct = (window_df&#91;'close'].max() - window_df&#91;'close'].min()) \/ window_df&#91;'close'].min() * 100\n            \n            # \u5224\u65ad\u662f\u5426\u76d8\u6574\n            signals_count = 0\n            if avg_bb_width &lt; 0.05: signals_count += 1\n            if abs(avg_rsi - 50) &lt; 15: signals_count += 1\n            if avg_atr_pct &lt; 1.5: signals_count += 1\n            if price_range_pct &lt; 8: signals_count += 1\n            \n            if signals_count >= 3:\n                consolidation_periods.append(window_df.index)\n    \n    # \u53ef\u89c6\u5316\u7ed3\u679c\n    plot_consolidation(df, consolidation_periods, stock_code)\n\nif __name__ == \"__main__\":\n    main()\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u683c\u529b\u7535\u5668 000651 &nbsp;\u6839\u636e\u4f60\u63d0\u4f9b\u7684\u4fe1\u606f\u548c\u4ee3\u7801\u7247\u6bb5\uff0c\u8fd9\u4e2a\u7a0b\u5e8f\u662f\u4e00\u4e2a\u80a1 <span class=\"readmore\"><a href=\"http:\/\/xc.ipyingshe.net:5347\/?p=6522\">Continue Reading<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,24],"tags":[],"class_list":["post-6522","post","type-post","status-publish","format-standard","hentry","category-2","category-24"],"_links":{"self":[{"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=\/wp\/v2\/posts\/6522","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=6522"}],"version-history":[{"count":1,"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=\/wp\/v2\/posts\/6522\/revisions"}],"predecessor-version":[{"id":6524,"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=\/wp\/v2\/posts\/6522\/revisions\/6524"}],"wp:attachment":[{"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=6522"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=6522"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/xc.ipyingshe.net:5347\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=6522"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}