Files
NewQuant/data/ analysis/Trend.ipynb

787 lines
2.4 MiB
Plaintext
Raw Normal View History

2025-09-16 09:59:38 +08:00
{
"cells": [
{
"metadata": {},
"cell_type": "raw",
"source": "# Please replace 'your_futures_data.csv' with the actual path to your CSV file",
"id": "fb1975346060eb6d"
},
{
"cell_type": "code",
"id": "initial_id",
"metadata": {
"collapsed": true,
"ExecuteTime": {
"end_time": "2025-09-15T15:54:36.868015Z",
"start_time": "2025-09-15T15:54:36.864065Z"
}
},
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import talib as ta # Make sure TA-Lib is installed: pip install TA-Lib\n",
"import statsmodels.api as sm\n",
"\n",
"import warnings\n",
"\n",
"# 忽略所有警告\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"# --- 0. Configure your file path ---\n",
"# Please replace 'your_futures_data.csv' with the actual path to your CSV file\n",
"file_path = '/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_SA/KQ_m@CZCE_SA_min15.csv'\n",
"\n",
"sns.set(style='whitegrid')\n",
"plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签\n",
"plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号\n"
],
"outputs": [],
"execution_count": 79
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-15T15:54:36.934311Z",
"start_time": "2025-09-15T15:54:36.894763Z"
}
},
"cell_type": "code",
"source": [
"\n",
"# --- 1. Data Loading and Preprocessing ---\n",
"def load_and_preprocess_data(file_path):\n",
" \"\"\"\n",
" Loads historical futures data and performs basic preprocessing.\n",
" Assumes data contains 'datetime', 'open', 'high', 'low', 'close', 'volume' columns.\n",
" \"\"\"\n",
" try:\n",
" df = pd.read_csv(file_path, parse_dates=['datetime'], index_col='datetime')\n",
" # Ensure data is sorted by time\n",
" df = df.sort_index()\n",
" # Check and handle missing values\n",
" initial_rows = len(df)\n",
" df.dropna(inplace=True)\n",
" if len(df) < initial_rows:\n",
" print(f\"Warning: Missing values found in data, deleted {initial_rows - len(df)} rows.\")\n",
"\n",
" # Check if necessary columns exist\n",
" required_columns = ['open', 'high', 'low', 'close', 'volume']\n",
" if not all(col in df.columns for col in required_columns):\n",
" raise ValueError(f\"CSV file is missing required columns. Please ensure it contains: {required_columns}\")\n",
"\n",
" print(f\"Successfully loaded {len(df)} rows of data.\")\n",
" print(\"First 5 rows of data:\")\n",
" print(df.head())\n",
" return df\n",
" except FileNotFoundError:\n",
" print(f\"Error: File '{file_path}' not found. Please check the path.\")\n",
" return None\n",
" except Exception as e:\n",
" print(f\"Error during data loading or preprocessing: {e}\")\n",
" return None\n",
"\n",
"\n",
"df_raw = load_and_preprocess_data(file_path)\n",
"df_raw = df_raw[df_raw.index <= '2024-01-01']"
],
"id": "1638e05ca7ef1ac8",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Successfully loaded 25662 rows of data.\n",
"First 5 rows of data:\n",
" open high low close volume open_oi \\\n",
"datetime \n",
"2020-12-31 14:45:00 1607.0 1611.0 1603.0 1611.0 19480.0 148833.0 \n",
"2021-01-04 09:00:00 1610.0 1636.0 1601.0 1620.0 55486.0 146448.0 \n",
"2021-01-04 09:15:00 1620.0 1620.0 1601.0 1604.0 30314.0 153373.0 \n",
"2021-01-04 09:30:00 1604.0 1606.0 1590.0 1595.0 30803.0 157091.0 \n",
"2021-01-04 09:45:00 1595.0 1601.0 1594.0 1600.0 10031.0 158730.0 \n",
"\n",
" close_oi underlying_symbol \n",
"datetime \n",
"2020-12-31 14:45:00 146448.0 CZCE.SA105 \n",
"2021-01-04 09:00:00 153373.0 CZCE.SA105 \n",
"2021-01-04 09:15:00 157091.0 CZCE.SA105 \n",
"2021-01-04 09:30:00 158730.0 CZCE.SA105 \n",
"2021-01-04 09:45:00 160031.0 CZCE.SA105 \n"
]
}
],
"execution_count": 80
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-15T15:54:38.886016Z",
"start_time": "2025-09-15T15:54:36.963147Z"
}
},
"cell_type": "code",
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import talib\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"# 设置图表样式,使其美观\n",
"sns.set_style('darkgrid')\n",
"plt.rcParams['figure.figsize'] = (20, 15) # 增大图表尺寸以获得更好的可读性\n",
"plt.rcParams['font.size'] = 12\n",
"\n",
"def analyze_market_regime(df_raw: pd.DataFrame,\n",
" adx_period: int = 14,\n",
" bb_period: int = 20,\n",
" ma_long_period: int = 100):\n",
" \"\"\"\n",
" 分析行情数据的趋势强度与市场机制。\n",
"\n",
" Args:\n",
" df_raw (pd.DataFrame): 包含 'open', 'high', 'low', 'close', 'volume' 列的行情数据。\n",
" adx_period (int): ADX指标的计算周期。\n",
" bb_period (int): 布林带指标的计算周期。\n",
" ma_long_period (int): 用于判断牛熊的长期均线周期。\n",
"\n",
" Returns:\n",
" pd.DataFrame: 增加了分析指标和市场状态的新DataFrame。\n",
" \"\"\"\n",
" df = df_raw.copy()\n",
"\n",
" # --- 1. 计算核心分析指标 ---\n",
"\n",
" # a) ADX (Average Directional Index) - 趋势强度\n",
" # ADX > 25 通常认为趋势较强\n",
" # ADX < 20 通常认为市场处于盘整或无趋势状态\n",
" df['adx'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=adx_period)\n",
"\n",
" # b) ATR Percent (Average True Range Percentage) - 标准化波动率\n",
" atr = talib.ATR(df['high'], df['low'], df['close'], timeperiod=14)\n",
" df['atr_pct'] = (atr / df['close']) * 100\n",
"\n",
" # c) Bollinger Bands Width - 价格通道宽度\n",
" upper, middle, lower = talib.BBANDS(df['close'], timeperiod=bb_period, nbdevup=2, nbdevdn=2, matype=0)\n",
" df['bb_width'] = ((upper - lower) / middle) * 100\n",
"\n",
" # d) 长期均线 - 宏观趋势方向\n",
" df['ma_long'] = talib.EMA(df['close'], timeperiod=ma_long_period)\n",
"\n",
" # --- 2. 定义市场机制 (Market Regime) ---\n",
"\n",
" # 条件定义\n",
" is_trending = df['adx'] > 25\n",
" is_choppy = df['adx'] < 20\n",
" # 布林带宽度处于过去120根K线的10%分位数以下,定义为挤压状态\n",
" is_squeeze = df['bb_width'] < df['bb_width'].rolling(120).quantile(0.1)\n",
"\n",
" is_bullish = df['close'] > df['ma_long']\n",
" is_bearish = df['close'] < df['ma_long']\n",
"\n",
" # 状态分配 (有优先级的选择)\n",
" conditions = [\n",
" is_squeeze, # 优先判断是否为挤压状态\n",
" is_trending & is_bullish,\n",
" is_trending & is_bearish,\n",
" is_choppy\n",
" ]\n",
"\n",
" choices = [\n",
" 'Low Volatility Squeeze', # 挤压\n",
" 'Strong Bull Trend', # 强趋势牛市\n",
" 'Strong Bear Trend', # 强趋势熊市\n",
" 'Choppy / Ranging' # 震荡\n",
" ]\n",
"\n",
" df['regime'] = np.select(conditions, choices, default='Transition') # 其他情况为过渡\n",
"\n",
" return df\n",
"\n",
"def plot_regime_analysis(df_analyzed: pd.DataFrame):\n",
" \"\"\"可视化市场机制分析结果\"\"\"\n",
"\n",
" fig, axes = plt.subplots(4, 1, sharex=True, gridspec_kw={'height_ratios': [3, 1, 1, 1]})\n",
" fig.suptitle('Market Regime and Trend Strength Analysis', fontsize=20, y=0.99)\n",
"\n",
" # --- 图1: 价格与市场机制 ---\n",
" ax1 = axes[0]\n",
" ax1.plot(df_analyzed.index, df_analyzed['close'], label='Close Price', color='orange', alpha=0.9, linewidth=1.5)\n",
" ax1.plot(df_analyzed.index, df_analyzed['ma_long'], label=f'EMA({df_analyzed[\"ma_long\"].first_valid_index()})', color='purple', linestyle='--', alpha=0.7)\n",
"\n",
" # 用不同颜色背景标注市场状态\n",
" regime_colors = {\n",
" 'Strong Bull Trend': 'rgba(0, 255, 0, 0.15)',\n",
" 'Strong Bear Trend': 'rgba(255, 0, 0, 0.15)',\n",
" 'Choppy / Ranging': 'rgba(128, 128, 128, 0.15)',\n",
" 'Low Volatility Squeeze': 'rgba(255, 165, 0, 0.2)',\n",
" 'Transition': 'rgba(255, 255, 255, 0)'\n",
" }\n",
"\n",
" last_regime = None\n",
" start_index = df_analyzed.index[0]\n",
" for i, (index, row) in enumerate(df_analyzed.iterrows()):\n",
" current_regime = row['regime']\n",
" if last_regime is None:\n",
" last_regime = current_regime\n",
"\n",
" if current_regime != last_regime or i == len(df_analyzed) - 1:\n",
" color_rgba_str = regime_colors.get(last_regime)\n",
"\n",
" # --- 【已修正】安全地解析RGBA字符串 ---\n",
" parts = color_rgba_str.replace('rgba(', '').replace(')', '').split(',')\n",
" r, g, b = [int(p) for p in parts[:3]]\n",
" alpha = float(parts[3])\n",
" face_color = (r/255, g/255, b/255)\n",
"\n",
" ax1.axvspan(start_index, index, facecolor=face_color, alpha=alpha, label=f'_{last_regime}')\n",
" start_index = index\n",
" last_regime = current_regime\n",
"\n",
" ax1.set_title('Price Action with Market Regimes', fontsize=16)\n",
" ax1.set_ylabel('Price', fontsize=12)\n",
" # 创建一个不会重复的图例\n",
" handles, labels = ax1.get_legend_handles_labels()\n",
" unique_labels = dict(zip(labels, handles))\n",
" ax1.legend(unique_labels.values(), unique_labels.keys())\n",
" ax1.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # --- 图2: ADX (趋势强度) ---\n",
" ax2 = axes[1]\n",
" ax2.plot(df_analyzed.index, df_analyzed['adx'], color='dodgerblue', label='ADX')\n",
" ax2.axhline(25, linestyle='--', color='red', alpha=0.7, label='Trending Threshold (25)')\n",
" ax2.axhline(20, linestyle='--', color='gray', alpha=0.7, label='Choppy Threshold (20)')\n",
" ax2.set_title('ADX - Trend Strength', fontsize=14)\n",
" ax2.set_ylabel('ADX Value', fontsize=12)\n",
" ax2.legend()\n",
" ax2.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # --- 图3: 布林带宽度 ---\n",
" ax3 = axes[2]\n",
" ax3.plot(df_analyzed.index, df_analyzed['bb_width'], color='green', label='Bollinger Band Width (%)')\n",
" ax3.set_title('Bollinger Bands Width - Volatility Squeeze', fontsize=14)\n",
" ax3.set_ylabel('Width (%)', fontsize=12)\n",
" ax3.legend()\n",
" ax3.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # --- 图4: ATR波动率 ---\n",
" ax4 = axes[3]\n",
" ax4.plot(df_analyzed.index, df_analyzed['atr_pct'], color='magenta', label='ATR (%)')\n",
" ax4.set_title('ATR - Normalized Volatility', fontsize=14)\n",
" ax4.set_ylabel('ATR (%)', fontsize=12)\n",
" ax4.legend()\n",
" ax4.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # 旋转X轴标签以防重叠\n",
" for label in ax4.get_xticklabels():\n",
" label.set_rotation(45)\n",
" label.set_ha('right')\n",
"\n",
" plt.tight_layout(rect=[0, 0, 1, 0.98]) # 调整布局以适应总标题\n",
" plt.show()\n",
"\n",
"def print_regime_statistics(df_analyzed: pd.DataFrame):\n",
" \"\"\"打印市场机制的统计数据\"\"\"\n",
"\n",
" regime_counts = df_analyzed['regime'].value_counts()\n",
" regime_percentage = df_analyzed['regime'].value_counts(normalize=True) * 100\n",
"\n",
" stats_df = pd.DataFrame({\n",
" 'Counts (Bars)': regime_counts,\n",
" 'Percentage (%)': regime_percentage\n",
" }).sort_values(by='Percentage (%)', ascending=False)\n",
"\n",
" print(\"--- Market Regime Statistics ---\")\n",
" print(stats_df.to_string(formatters={'Percentage (%)': '{:,.2f}%'.format}))\n",
" print(\"\\n\" + \"=\"*30)\n",
" print(\"Analysis Summary:\")\n",
" strong_trend_pct = regime_percentage.get('Strong Bull Trend', 0) + regime_percentage.get('Strong Bear Trend', 0)\n",
" print(f\"- Strong Trend (Bull+Bear) constitutes {strong_trend_pct:.2f}% of the time.\")\n",
" print(f\"- The market is in a Choppy / Ranging state for {regime_percentage.get('Choppy / Ranging', 0):.2f}% of the time.\")\n",
" print(f\"- Low Volatility Squeeze, often preceding major moves, occurs {regime_percentage.get('Low Volatility Squeeze', 0):.2f}% of the time.\")\n",
" print(f\"- The remaining {regime_percentage.get('Transition', 0):.2f}% is in a transitional state.\")\n",
" print(\"=\"*30)\n",
"\n",
"\n",
"# --- 如何使用 ---\n",
"\n",
"# 1. 确保您的df_raw已准备好\n",
"# 您的 df_raw DataFrame 应该在这里被定义或加载\n",
"# 例如:\n",
"# df_raw = pd.read_csv('your_crypto_futures_data.csv', index_col='timestamp', parse_dates=True)\n",
"# 确保列名是小写的 'open', 'high', 'low', 'close', 'volume'\n",
"\n",
"# 2. 运行分析\n",
"# 检查df_raw是否存在如果不存在则会报错\n",
"if 'df_raw' in locals() and isinstance(df_raw, pd.DataFrame):\n",
" df_analyzed = analyze_market_regime(df_raw)\n",
"\n",
" # 3. 可视化结果\n",
" plot_regime_analysis(df_analyzed)\n",
"\n",
" # 4. 打印统计报告\n",
" print_regime_statistics(df_analyzed)\n",
"else:\n",
" print(\"错误: 请先加载您的行情数据到名为 'df_raw' 的Pandas DataFrame中。\")\n",
" print(\"df_raw需要包含列: ['open', 'high', 'low', 'close', 'volume'] 且索引为时间序列。\")"
],
"id": "a8b2c75e6f3a8e2e",
"outputs": [
{
"data": {
"text/plain": [
"<Figure size 2000x1500 with 4 Axes>"
],
"image/png": "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MGTIkJD9Ka5cfv/9d8aPH8/y5cs5ePAgcXFxtGzZkttvv73I8ym4vx9//DEzZsxg27ZtGIZBnTp1uOqqqxg6dOgJ7+fJatiwIQAjRozg3nvvZcWKFUyZMoW1a9dy+PBhqlevzqJFi0KWOXToEB9++CFLly5l9+7dZGZmUqVKFVq0aMGAAQMK/Uhn9+7dBerpd999x6RJk1i/fj0pKSlUq1aNzp07c/fdd1OjRo0i856SksK7777LwoUL2bt3L9HR0TRs2JABAwZw+eWXn3TZ5O3SvE6dOqVePu/+Bg0bNqxAurzn7IleexcuXMisWbOsczEiIoI6derQvXt3hg4dSuXKlcPm8eGHH2b69OnUqlWLRYsWkZqayoQJE5g/fz579uzB4XBYZRoM9hZl0aJFTJ48mY0bN5KVlUWNGjXo3r07t9xyC0lJSQXOqRMtp/xycnKYNGkSs2fPtgLU9evXp1+/fgwcOBCH49T8hDRjxgwAKleuTNeuXfH7/URHR5ORkcGMGTO44IILilw+//m2bt063n//fVavXs3Ro0dJSEigffv23HXXXdSvX7/Q9ezatYsFCxbwww8/sGXLFg4fPgwEenq48MILufbaa+nSpUup9+/o0aN06dIFj8fDgAEDePLJJ4tMv2jRIu6++24AXnrpJa644gprXk5ODlOnTmXBggX89ttvpKWlER0dTUJCArVr16ZTp0707NmTs88+O2Sd+etkOAsWLGD69Ols2LDBer5JTEykevXqtG3blu7du9O8efNS77+IiIiIyJ+BAuAiIiIiUi707duX999/n19++YXffvuN888/P2T+1q1brZZlffv2ZdOmTUWub+XKlWGDBx6Phx07drBjxw5mzJjBHXfcwd///vdi87d9+3ZuueUW9uzZU4q9Ct3uQw89ZLVwv/vuuxk1alRImhUrVjBq1CiSk5NDph86dIjFixezePFiunbtyksvvUR0dPQJ5eNUMwzD+hihqODKuHHjeOmll/B4PCHTd+/eze7du5kxYwZ333039913X9jl09PTufXWW/n5559Dpm/cuJGNGzcye/bsYoMUpTFjxgweeeSRkPzu3LmT999/n2+++YYPP/yQpKQkxo0bx/PPP2+1kIVAUOa9995jxYoVTJo0qdBjtXfvXu6++242b94cMj05OZk1a9awZs0apkyZwttvv8255557Qvvx2GOPFfgYIriN5ORk1q1bx4cffsjYsWNL1Gp3wYIFPPDAA2RlZVnTjhw5wsKFC1m8eDEvvPBCSPAnr4yMDO644w5Wr14dMv2XX37hl19+Yfbs2Tz11FOl3MNT76WXXuKtt94qMs2sWbN4/PHHyczMDJm+f/9+5s6dy9y5c7nuuut44oknig06vvjii7zzzjsh0/bs2cPHH3/M/Pnz+fDDDwsNAm7bto3hw4dz8OBBa1pOTg4rVqxgxYoVfPvtt1x00UVFbr84LpfL+vfvv/9OvXr1Tmp9pVWSa29KSgojR47k+++/D5nudruta8RHH33E2LFji/1I4/fff+e2224rsL3Vq1ezevVqfv755yKHenjiiSf46KOPCuzD+PHj+eKLLwoc61Pl8OHD3HbbbQXujevXr2f9+vUsW7aMsWPHYrOdXEeCbrebOXPmAHDZZZdZ9aNXr15Mnz6d2bNn89BDD5U
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- Market Regime Statistics ---\n",
" Counts (Bars) Percentage (%)\n",
"regime \n",
"Strong Bull Trend 4013 24.23%\n",
"Choppy / Ranging 3998 24.14%\n",
"Strong Bear Trend 3482 21.02%\n",
"Transition 3105 18.75%\n",
"Low Volatility Squeeze 1964 11.86%\n",
"\n",
"==============================\n",
"Analysis Summary:\n",
"- Strong Trend (Bull+Bear) constitutes 45.25% of the time.\n",
"- The market is in a Choppy / Ranging state for 24.14% of the time.\n",
"- Low Volatility Squeeze, often preceding major moves, occurs 11.86% of the time.\n",
"- The remaining 18.75% is in a transitional state.\n",
"==============================\n"
]
}
],
"execution_count": 81
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-15T15:54:40.243472Z",
"start_time": "2025-09-15T15:54:38.921451Z"
}
},
"cell_type": "code",
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import talib\n",
"from scipy.stats import linregress\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"def analyze_long_term_regime(df_raw: pd.DataFrame,\n",
" ma_period: int = 120,\n",
" r2_period: int = 120):\n",
" \"\"\"\n",
" 使用长周期斜率和R-squared来分析市场的结构性特征。\n",
"\n",
" Args:\n",
" df_raw (pd.DataFrame): 原始行情数据。\n",
" ma_period (int): 用于计算斜率的移动平均线周期,建议 60-250。\n",
" r2_period (int): 用于计算R-squared的线性回归周期建议 60-250。\n",
" \"\"\"\n",
" df = df_raw.copy()\n",
" close = df['close']\n",
"\n",
" # --- 1. 计算长周期归一化斜率 ---\n",
" ma = talib.EMA(close, timeperiod=ma_period)\n",
" # 斜率 = (MA_today - MA_period_ago) / (MA_today * period) * 10000\n",
" # 这种计算方式更稳定,反映了周期内的平均变化率\n",
" ma_diff = ma.diff(ma_period)\n",
" df['ma_slope'] = (ma_diff / ma) / ma_period * 10000\n",
"\n",
" # --- 2. 计算长周期R-squared ---\n",
" def rolling_r_squared(y):\n",
" if len(y) < 2 or np.std(y) == 0: return 0.0\n",
" x = np.arange(len(y))\n",
" slope, intercept, r_value, p_value, std_err = linregress(x, y)\n",
" return r_value**2\n",
" df['r_squared'] = close.rolling(window=r2_period).apply(rolling_r_squared, raw=True)\n",
"\n",
" # --- 3. 定义市场政权 (Regime) ---\n",
" slope_threshold = 1.5 # 斜率阈值,需要根据标的波动性调整\n",
" r2_threshold = 0.5 # R-squared阈值0.5-0.7是很好的分界线\n",
"\n",
" is_strong_slope = abs(df['ma_slope']) > slope_threshold\n",
" is_weak_slope = abs(df['ma_slope']) <= slope_threshold\n",
"\n",
" is_linear = df['r_squared'] > r2_threshold\n",
" is_nonlinear = df['r_squared'] <= r2_threshold\n",
"\n",
" conditions = [\n",
" is_strong_slope & is_linear & (df['ma_slope'] > 0), # 高斜率 + 高R2 (多)\n",
" is_strong_slope & is_linear & (df['ma_slope'] < 0), # 高斜率 + 高R2 (空)\n",
" is_weak_slope & is_nonlinear, # 低斜率 + 低R2\n",
" is_weak_slope & is_linear # 低斜率 + 高R2 (阴跌/稳涨)\n",
" ]\n",
"\n",
" choices = [\n",
" 'Smooth Bull Trend',\n",
" 'Smooth Bear Trend',\n",
" 'Directionless Chop',\n",
" 'Grinding Trend' # 包含了阴跌和稳涨\n",
" ]\n",
"\n",
" df['regime'] = np.select(conditions, choices, default='Volatile Trend') # 剩下的就是高斜率+低R2\n",
"\n",
" return df\n",
"\n",
"def plot_long_term_dashboard(df_final: pd.DataFrame):\n",
" \"\"\"可视化长周期分析结果\"\"\"\n",
" sns.set_style('darkgrid')\n",
" plt.rcParams['figure.figsize'] = (20, 16)\n",
" plt.rcParams['font.size'] = 12\n",
"\n",
" fig, axes = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]})\n",
" fig.suptitle('Long-Term Market Structure Dashboard (Slope & R-Squared)', fontsize=24, y=0.99)\n",
"\n",
" # --- 图1: 价格与最终的市场机制 ---\n",
" ax1 = axes[0]\n",
" ax1.plot(df_final.index, df_final['close'], label='Close Price', color='black', alpha=0.9, linewidth=1.5)\n",
"\n",
" regime_colors = {\n",
" 'Smooth Bull Trend': 'rgba(0, 255, 0, 0.2)',\n",
" 'Smooth Bear Trend': 'rgba(255, 0, 0, 0.2)',\n",
" 'Directionless Chop': 'rgba(128, 128, 128, 0.2)',\n",
" 'Grinding Trend': 'rgba(255, 165, 0, 0.2)', # 橙色代表慢速趋势\n",
" 'Volatile Trend': 'rgba(138, 43, 226, 0.2)' # 紫色代表混乱趋势\n",
" }\n",
"\n",
" last_regime, start_index = None, df_final.index[0]\n",
" for i, (index, row) in enumerate(df_final.iterrows()):\n",
" current_regime = row['regime']\n",
" if last_regime is None: last_regime = current_regime\n",
" if current_regime != last_regime or i == len(df_final) - 1:\n",
" color_rgba_str = regime_colors.get(last_regime)\n",
" parts = color_rgba_str.replace('rgba(', '').replace(')', '').split(',')\n",
" r, g, b = [int(p) for p in parts[:3]]; alpha = float(parts[3])\n",
" face_color = (r/255, g/255, b/255)\n",
" ax1.axvspan(start_index, index, facecolor=face_color, alpha=alpha)\n",
" start_index, last_regime = index, current_regime\n",
"\n",
" ax1.set_title('1. Price Action with Classified Long-Term Regimes', fontsize=16)\n",
" ax1.set_ylabel('Price', fontsize=12)\n",
" # 手动创建图例\n",
" patches = [plt.Rectangle((0,0),1,1, color=tuple(int(p)/255 for p in c.replace('rgba(', '').replace(')', '').split(',')[:3]) + (float(c.replace('rgba(', '').replace(')', '').split(',')[3]),)) for c in regime_colors.values()]\n",
" ax1.legend(patches, regime_colors.keys(), loc='upper left')\n",
" ax1.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # --- 图2: 双维度罗盘 (Slope vs R-Squared) ---\n",
" ax2 = axes[1]\n",
" ax2_twin = ax2.twinx()\n",
"\n",
" p1, = ax2.plot(df_final.index, df_final['r_squared'], color='green', label='Stability (R-Squared)')\n",
" ax2.set_ylabel('R-Squared (0 to 1)', fontsize=12, color='green')\n",
" ax2.tick_params(axis='y', labelcolor='green')\n",
" ax2.set_ylim(0, 1)\n",
" ax2.axhline(0.5, linestyle=':', color='darkgreen', alpha=0.7)\n",
"\n",
" p2, = ax2_twin.plot(df_final.index, df_final['ma_slope'], color='dodgerblue', alpha=0.8, label='Velocity (MA Slope)')\n",
" ax2_twin.axhline(0, linestyle='--', color='gray', alpha=0.7)\n",
" ax2_twin.set_ylabel('Normalized Slope (Basis Points)', fontsize=12, color='dodgerblue')\n",
" ax2_twin.tick_params(axis='y', labelcolor='dodgerblue')\n",
"\n",
" ax2.set_title('2. The Two-Dimensional Compass: Trend Quality & Velocity', fontsize=16)\n",
" ax2.legend(handles=[p1, p2], loc='upper left')\n",
" ax2.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" plt.tight_layout(rect=[0, 0, 1, 0.98])\n",
" plt.show()\n",
"\n",
"# --- 如何使用 ---\n",
"if 'df_raw' in locals() and isinstance(df_raw, pd.DataFrame):\n",
" # 【关键】使用足够长的周期来分析中长线结构\n",
" df_analyzed_longterm = analyze_long_term_regime(df_raw, ma_period=120, r2_period=120)\n",
" plot_long_term_dashboard(df_analyzed_longterm)\n",
"else:\n",
" print(\"错误: 请先加载您的行情数据到名为 'df_raw' 的Pandas DataFrame中。\")"
],
"id": "c0be0ac8d7de7b12",
"outputs": [
{
"data": {
"text/plain": [
"<Figure size 2000x1600 with 3 Axes>"
],
"image/png": "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
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 82
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-15T15:54:41.437286Z",
"start_time": "2025-09-15T15:54:40.271690Z"
}
},
"cell_type": "code",
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import talib\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"# --- 分析函数 (保持不变) ---\n",
"def analyze_trend_and_volatility_regime(df_raw: pd.DataFrame,\n",
" ma_long_period: int = 120,\n",
" atr_fast_period: int = 20,\n",
" atr_slow_period: int = 100):\n",
" \"\"\"\n",
" 结合长期趋势方向和波动率机制,构建一个二维的市场状态罗盘。\n",
" \"\"\"\n",
" df = df_raw.copy()\n",
" df['ma_long'] = talib.EMA(df['close'], timeperiod=ma_long_period)\n",
" is_bullish_trend = df['close'] > df['ma_long']\n",
" is_bearish_trend = df['close'] < df['ma_long']\n",
" atr = talib.ATR(df['high'], df['low'], df['close'], timeperiod=14)\n",
" atr_pct = (atr / df['close']) * 100\n",
" atr_fast_ma = talib.EMA(atr_pct, timeperiod=atr_fast_period)\n",
" atr_slow_ma = talib.EMA(atr_pct, timeperiod=atr_slow_period)\n",
" is_high_volatility = atr_fast_ma > atr_slow_ma\n",
" is_low_volatility = atr_fast_ma <= atr_slow_ma\n",
" conditions = [\n",
" is_bullish_trend & is_high_volatility,\n",
" is_bearish_trend & is_high_volatility,\n",
" is_bullish_trend & is_low_volatility,\n",
" is_bearish_trend & is_low_volatility\n",
" ]\n",
" choices = [\n",
" 'Bullish Breakout',\n",
" 'Bearish Breakout',\n",
" 'Bullish Consolidation',\n",
" 'Bearish Consolidation'\n",
" ]\n",
" df['regime'] = np.select(conditions, choices, default='Transition')\n",
" df['atr_fast_ma'] = atr_fast_ma\n",
" df['atr_slow_ma'] = atr_slow_ma\n",
" return df\n",
"\n",
"# --- 【核心修改】调整后的可视化函数 ---\n",
"def plot_trend_volatility_dashboard(df_final: pd.DataFrame):\n",
" \"\"\"\n",
" 可视化二维市场罗盘的分析结果。\n",
" 所有Breakout标记为红色所有Consolidation标记为灰色。\n",
" \"\"\"\n",
" sns.set_style('darkgrid')\n",
" plt.rcParams['figure.figsize'] = (20, 12)\n",
" plt.rcParams['font.size'] = 12\n",
"\n",
" fig, axes = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]})\n",
" fig.suptitle('The Market Compass: Breakout vs. Consolidation Regimes', fontsize=24, y=0.99)\n",
"\n",
" # --- 图1: 价格与两个核心政权 ---\n",
" ax1 = axes[0]\n",
" ax1.plot(df_final.index, df_final['close'], label='Close Price', color='black', alpha=0.9, linewidth=1.5)\n",
" ax1.plot(df_final.index, df_final['ma_long'], label=f'Long-Term MA ({df_final.columns.get_loc(\"ma_long\")})', color='purple', linestyle='--', alpha=0.7)\n",
"\n",
" # --- 【核心修改】简化颜色映射 ---\n",
" regime_colors = {\n",
" 'Bullish Breakout': 'rgba(255, 0, 0, 0.2)', # 红色: 突破行情 (可交易)\n",
" 'Bearish Breakout': 'rgba(255, 0, 0, 0.2)', # 红色: 突破行情 (可交易)\n",
" 'Bullish Consolidation': 'rgba(128, 128, 128, 0.2)', # 灰色: 盘整行情 (应规避)\n",
" 'Bearish Consolidation': 'rgba(128, 128, 128, 0.2)' # 灰色: 盘整行情 (应规避)\n",
" }\n",
"\n",
" last_regime, start_index = None, df_final.index[0]\n",
" for i, (index, row) in enumerate(df_final.iterrows()):\n",
" current_regime = row['regime']\n",
" if last_regime is None: last_regime = current_regime\n",
" if current_regime != last_regime or i == len(df_final) - 1:\n",
" color_rgba_str = regime_colors.get(last_regime, 'rgba(255,255,255,0)') # 过渡区为透明\n",
" parts = color_rgba_str.replace('rgba(', '').replace(')', '').split(',')\n",
" r, g, b = [int(p) for p in parts[:3]]; alpha = float(parts[3])\n",
" face_color = (r/255, g/255, b/255)\n",
" ax1.axvspan(start_index, index, facecolor=face_color, alpha=alpha)\n",
" start_index, last_regime = index, current_regime\n",
"\n",
" ax1.set_title('1. Price Action with Combined Regimes', fontsize=16)\n",
" ax1.set_ylabel('Price')\n",
"\n",
" # --- 【核心修改】创建简化的图例 ---\n",
" legend_patches = [\n",
" plt.Rectangle((0,0),1,1, color=(1,0,0,0.2)), # 红色块\n",
" plt.Rectangle((0,0),1,1, color=(0.5,0.5,0.5,0.2)) # 灰色块\n",
" ]\n",
" ax1.legend(legend_patches,\n",
" ['Breakout Regime (High Volatility, Time to Trade)', 'Consolidation Regime (Low Volatility, Time to Wait)'],\n",
" loc='upper left')\n",
" ax1.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # --- 图2: 双重过滤系统 (保持不变,用于观察内部机制) ---\n",
" ax2 = axes[1]\n",
" ax2.set_title('2. The Two Filters: Trend Direction (Price vs MA) & Volatility (ATR MA vs ATR MA)', fontsize=16)\n",
" ax2.plot(df_final.index, df_final['atr_fast_ma'], color='dodgerblue', label='Fast ATR MA')\n",
" ax2.plot(df_final.index, df_final['atr_slow_ma'], color='orangered', label='Slow ATR MA')\n",
" ax2.set_ylabel('ATR (%)', color='dodgerblue')\n",
" ax2.tick_params(axis='y', labelcolor='dodgerblue')\n",
" ax2.legend(loc='upper left')\n",
" ax2_twin = ax2.twinx()\n",
" ax2_twin.plot(df_final.index, df_final['close'], color='black', alpha=0.3, label='Close Price (for Trend Ref.)')\n",
" ax2_twin.plot(df_final.index, df_final['ma_long'], color='purple', linestyle='--', alpha=0.4, label='Long-Term MA (for Trend Ref.)')\n",
" ax2_twin.set_ylabel('Price', color='gray')\n",
" ax2_twin.tick_params(axis='y', labelcolor='gray')\n",
" ax2_twin.legend(loc='upper right')\n",
"\n",
" plt.tight_layout(rect=[0, 0, 1, 0.98])\n",
" plt.show()\n",
"\n",
"# --- 如何使用 ---\n",
"if 'df_raw' in locals() and isinstance(df_raw, pd.DataFrame):\n",
" df_final_analyzed = analyze_trend_and_volatility_regime(df_raw)\n",
" plot_trend_volatility_dashboard(df_final_analyzed)\n",
"else:\n",
" print(\"错误: 请先加载您的行情数据到名为 'df_raw' 的Pandas DataFrame中。\")"
],
"id": "661ad364ed15c5",
"outputs": [
{
"data": {
"text/plain": [
"<Figure size 2000x1200 with 3 Axes>"
],
"image/png": "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
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 83
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-15T15:54:42.155376Z",
"start_time": "2025-09-15T15:54:41.461145Z"
}
},
"cell_type": "code",
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import talib\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"def calculate_dual_momentum_metrics(df_raw: pd.DataFrame,\n",
" roc_medium_period: int = 60,\n",
" roc_long_period: int = 200):\n",
" \"\"\"\n",
" 使用双重时间框架的ROC来区分宏观趋势和中观摆动。\n",
" \"\"\"\n",
" df = df_raw.copy()\n",
"\n",
" # 1. 计算中观动量 (用于交易执行)\n",
" df[f'roc_medium_{roc_medium_period}'] = talib.ROC(df['close'], timeperiod=roc_medium_period)\n",
"\n",
" # 2. 计算宏观动量 (用于趋势过滤)\n",
" df[f'roc_long_{roc_long_period}'] = talib.ROC(df['close'], timeperiod=roc_long_period)\n",
"\n",
" return df\n",
"\n",
"def plot_dual_momentum_dashboard(df_final: pd.DataFrame, roc_medium_period: int = 60, roc_long_period: int = 200):\n",
" \"\"\"\n",
" 可视化双核动量系统。\n",
" \"\"\"\n",
" sns.set_style('darkgrid')\n",
" plt.rcParams['figure.figsize'] = (20, 12)\n",
" plt.rcParams['font.size'] = 12\n",
"\n",
" fig, axes = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]})\n",
" fig.suptitle('Dual Momentum Framework: Macro Gravity vs. Medium-Term Thrust', fontsize=24, y=0.99)\n",
"\n",
" # --- 图1: 价格与“交易窗口” ---\n",
" ax1 = axes[0]\n",
" ax1.plot(df_final.index, df_final['close'], label='Close Price', color='black', alpha=0.9, linewidth=1.5)\n",
"\n",
" # 定义交易窗口:当中观和宏观动量同向时\n",
" is_bullish_window = (df_final[f'roc_medium_{roc_medium_period}'] > 0) & (df_final[f'roc_long_{roc_long_period}'] > 0)\n",
" is_bearish_window = (df_final[f'roc_medium_{roc_medium_period}'] < 0) & (df_final[f'roc_long_{roc_long_period}'] < 0)\n",
"\n",
" ax1.fill_between(df_final.index, df_final['close'].min(), df_final['close'].max(),\n",
" where=is_bullish_window, color='green', alpha=0.15, label='Bullish Trading Window')\n",
" ax1.fill_between(df_final.index, df_final['close'].min(), df_final['close'].max(),\n",
" where=is_bearish_window, color='red', alpha=0.15, label='Bearish Trading Window')\n",
"\n",
" ax1.set_title('1. Price with Highlighted Trading Windows (When Momenta Align)', fontsize=16)\n",
" ax1.set_ylabel('Price')\n",
" ax1.legend()\n",
" ax1.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" # --- 图2: 双核动量指标 ---\n",
" ax2 = axes[1]\n",
" ax2.plot(df_final.index, df_final[f'roc_medium_{roc_medium_period}'], color='dodgerblue',\n",
" label=f'Medium-Term Thrust (ROC {roc_medium_period})')\n",
" ax2.plot(df_final.index, df_final[f'roc_long_{roc_long_period}'], color='purple', linewidth=2.5,\n",
" label=f'Macro Gravity (ROC {roc_long_period})')\n",
"\n",
" ax2.axhline(0, linestyle='--', color='black', linewidth=1.5)\n",
" ax2.set_title('2. The Two Momenta: Macro vs. Medium-Term', fontsize=16)\n",
" ax2.set_ylabel('ROC (%)')\n",
" ax2.legend()\n",
" ax2.grid(True, linestyle='--', alpha=0.6)\n",
"\n",
" plt.tight_layout(rect=[0, 0, 1, 0.98])\n",
" plt.show()\n",
"\n",
"# --- 如何使用 ---\n",
"if 'df_raw' in locals() and isinstance(df_raw, pd.DataFrame):\n",
" df_dual_momentum_analyzed = calculate_dual_momentum_metrics(df_raw, roc_medium_period=60, roc_long_period=200)\n",
" plot_dual_momentum_dashboard(df_dual_momentum_analyzed, roc_medium_period=60, roc_long_period=200)\n",
"else:\n",
" print(\"错误: 请先加载您的行情数据到名为 'df_raw' 的Pandas DataFrame中。\")"
],
"id": "7f19075d89def898",
"outputs": [
{
"data": {
"text/plain": [
"<Figure size 2000x1200 with 2 Axes>"
],
"image/png": "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
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 84
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}