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": {
2025-09-20 00:04:51 +08:00
"end_time": "2025-09-17T03:17:57.076612Z",
"start_time": "2025-09-17T03:17:57.073753Z"
2025-09-16 09:59:38 +08:00
}
},
"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": [],
2025-09-20 00:04:51 +08:00
"execution_count": 9
2025-09-16 09:59:38 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-09-20 00:04:51 +08:00
"end_time": "2025-09-17T03:17:57.134035Z",
"start_time": "2025-09-17T03:17:57.097163Z"
2025-09-16 09:59:38 +08:00
}
},
"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",
2025-09-20 00:04:51 +08:00
"df_raw = df_raw[df_raw.index >= '2024-01-01']"
2025-09-16 09:59:38 +08:00
],
"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"
]
}
],
2025-09-20 00:04:51 +08:00
"execution_count": 10
2025-09-16 09:59:38 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-09-20 00:04:51 +08:00
"end_time": "2025-09-17T03:17:57.885755Z",
"start_time": "2025-09-17T03:17:57.158908Z"
}
},
"cell_type": "code",
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"from collections import deque\n",
"\n",
"def calculate_dp_states(df_raw: pd.DataFrame,\n",
" lookback_period: int = 120,\n",
" decay_factor_long: float = 0.95,\n",
" decay_factor_short: float = 0.95):\n",
" \"\"\"\n",
" 根据给定的DataFrame计算每个Bar的DP状态序列。\n",
" 完全复现策略中的核心计算逻辑。\n",
"\n",
" Args:\n",
" df_raw (pd.DataFrame): 原始行情数据,必须包含 'close', 'volume',\n",
" 和 'close_oi' (或 'open_interest') 列。\n",
" lookback_period (int): 用于计算滚动平均成交量的回看周期。\n",
" decay_factor_long (float): 多头DP状态的衰减因子。\n",
" decay_factor_short (float): 空头DP状态的衰减因子。\n",
"\n",
" Returns:\n",
" pd.DataFrame: 增加了 'dp_long', 'dp_short', 'dominance', 'activity' 列的新DataFrame。\n",
" \"\"\"\n",
" df = df_raw.copy()\n",
"\n",
" # --- 1. 准备工作 ---\n",
" # 确保持仓量列存在,兼容两种常见的列名\n",
" if 'close_oi' not in df.columns and 'open_interest' in df.columns:\n",
" df.rename(columns={'open_interest': 'close_oi'}, inplace=True)\n",
" elif 'close_oi' not in df.columns:\n",
" raise ValueError(\"DataFrame中必须包含 'close_oi' 或 'open_interest' 列。\")\n",
"\n",
" # 初始化输出列和中间变量\n",
" dp_long_series = np.zeros(len(df))\n",
" dp_short_series = np.zeros(len(df))\n",
"\n",
" dp_long = 0.0\n",
" dp_short = 0.0\n",
"\n",
" # 使用deque高效地维护滚动成交量\n",
" volume_history = deque(maxlen=lookback_period)\n",
"\n",
" print(\"开始计算DP状态序列...\")\n",
" # --- 2. 逐行遍历计算 ---\n",
" # 使用.itertuples()以获得高性能的行遍历\n",
" for i, row in enumerate(df.itertuples()):\n",
"\n",
" # --- a. 数据预热和准备 ---\n",
" if i == 0:\n",
" volume_history.append(row.volume)\n",
" continue # 跳过第一行, 因为无法计算change\n",
"\n",
" previous_row = df.iloc[i-1]\n",
"\n",
" # --- b. 计算核心指标 ---\n",
" price_change = row.close - previous_row['close']\n",
" oi_change = row.close_oi - previous_row['close_oi']\n",
"\n",
" # 更新并计算滚动平均成交量\n",
" volume_history.append(row.volume)\n",
" recent_avg_volume = np.mean(list(volume_history))\n",
"\n",
" epsilon = 1e-9\n",
" volume_factor = row.volume / (recent_avg_volume + epsilon)\n",
" volume_factor = np.clip(volume_factor, 0, 3)\n",
"\n",
" # --- c. 计算基础分数 (完全复现您的逻辑) ---\n",
" bullish_base_score = 0\n",
" bearish_base_score = 0\n",
" if price_change > 0:\n",
" if oi_change > 0:\n",
" bullish_base_score = 2 # 多头开仓\n",
" elif oi_change < 0:\n",
" bullish_base_score = 1 # 空头平仓\n",
" elif price_change < 0:\n",
" if oi_change > 0:\n",
" bearish_base_score = 2 # 空头开仓\n",
" elif oi_change < 0:\n",
" bearish_base_score = 1 # 多头平仓\n",
"\n",
" # --- d. 计算冲量并应用状态转移方程 ---\n",
" final_bullish_impulse = bullish_base_score * volume_factor\n",
" final_bearish_impulse = bearish_base_score * volume_factor\n",
"\n",
" dp_long = dp_long * decay_factor_long + final_bullish_impulse\n",
" dp_short = dp_short * decay_factor_short + final_bearish_impulse\n",
"\n",
" # --- e. 记录当前Bar的状态值 ---\n",
" dp_long_series[i] = dp_long\n",
" dp_short_series[i] = dp_short\n",
"\n",
" # 打印进度 (可选)\n",
" if (i + 1) % 1000 == 0:\n",
" print(f\"已处理 {i + 1}/{len(df)} 行...\")\n",
"\n",
" print(\"计算完成。\")\n",
"\n",
" # --- 3. 将计算结果添加到DataFrame中 ---\n",
" df['dp_long'] = dp_long_series\n",
" df['dp_short'] = dp_short_series\n",
" df['dominance'] = df['dp_long'] - df['dp_short']\n",
" df['activity'] = df['dp_long'] + df['dp_short']\n",
"\n",
" return df\n",
"\n",
"# --- 如何使用 ---\n",
"\n",
"# 1. 准备你的 df_raw DataFrame\n",
"# 确保它至少包含 'close', 'volume', 'close_oi' (或 'open_interest') 列\n",
"# 并且索引是时间序列\n",
"#\n",
"# 为了演示, 我们创建一个虚拟的df_raw\n",
"# 在实际使用中,请替换成你自己的数据\n",
"\n",
"\n",
"# 2. 调用函数进行计算\n",
"# 您可以根据策略的参数来设置 lookback_period 和 decay_factor\n",
"df_with_dp = calculate_dp_states(df_raw,\n",
" lookback_period=120,\n",
" decay_factor_long=0.95,\n",
" decay_factor_short=0.95)\n",
"\n",
"# 3. 查看结果\n",
"print(\"\\n--- 计算结果预览 (最后10行) ---\")\n",
"print(df_with_dp[['close', 'close_oi', 'dp_long', 'dp_short', 'dominance', 'activity']].tail(10))\n",
"\n",
"# 4. (可选) 可视化结果\n",
"def plot_dp_dashboard(df_plot: pd.DataFrame):\n",
" fig, axes = plt.subplots(3, 1, sharex=True, figsize=(20, 15), gridspec_kw={'height_ratios': [2, 1, 1]})\n",
"\n",
" # 图1: 价格\n",
" ax1 = axes[0]\n",
" ax1.plot(df_plot.index, df_plot['close'], label='Close Price', color='black')\n",
" ax1.set_title('Price Action')\n",
" ax1.set_ylabel('Price')\n",
" ax1.legend()\n",
"\n",
" # 图2: DP Long vs Short\n",
" ax2 = axes[1]\n",
" ax2.plot(df_plot.index, df_plot['dp_long'], label='DP Long', color='green')\n",
" ax2.plot(df_plot.index, df_plot['dp_short'], label='DP Short', color='red')\n",
" ax2.set_title('DP States: Long vs Short Momentum')\n",
" ax2.set_ylabel('DP Value')\n",
" ax2.legend()\n",
"\n",
" # 图3: Dominance & Activity\n",
" ax3 = axes[2]\n",
" ax3.plot(df_plot.index, df_plot['dominance'], label='Dominance (Long - Short)', color='blue')\n",
" ax3.axhline(0, linestyle='--', color='gray')\n",
" ax3_twin = ax3.twinx() # 使用次Y轴\n",
" ax3_twin.plot(df_plot.index, df_plot['activity'], label='Activity (Long + Short)', color='orange', alpha=0.6)\n",
" ax3.set_title('Dominance and Activity')\n",
" ax3.set_ylabel('Dominance Value', color='blue')\n",
" ax3_twin.set_ylabel('Activity Value', color='orange')\n",
"\n",
" # 合并图例\n",
" lines, labels = ax3.get_legend_handles_labels()\n",
" lines2, labels2 = ax3_twin.get_legend_handles_labels()\n",
" ax3.legend(lines + lines2, labels + labels2, loc=0)\n",
"\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"plot_dp_dashboard(df_with_dp)"
],
"id": "bae082b77f4e2263",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"开始计算DP状态序列...\n",
"已处理 1000/9100 行...\n",
"已处理 2000/9100 行...\n",
"已处理 3000/9100 行...\n",
"已处理 4000/9100 行...\n",
"已处理 5000/9100 行...\n",
"已处理 6000/9100 行...\n",
"已处理 7000/9100 行...\n",
"已处理 8000/9100 行...\n",
"已处理 9000/9100 行...\n",
"计算完成。\n",
"\n",
"--- 计算结果预览 (最后10行) ---\n",
" close close_oi dp_long dp_short dominance \\\n",
"datetime \n",
"2025-08-26 09:15:00 1335.0 1390704.0 10.686273 11.228222 -0.541949 \n",
"2025-08-26 09:30:00 1332.0 1393202.0 10.151959 11.470161 -1.318202 \n",
"2025-08-26 09:45:00 1335.0 1393247.0 10.293139 10.896653 -0.603514 \n",
"2025-08-26 10:00:00 1336.0 1394059.0 10.231764 10.351820 -0.120056 \n",
"2025-08-26 10:30:00 1334.0 1393454.0 9.720176 10.035671 -0.315495 \n",
"2025-08-26 10:45:00 1333.0 1393766.0 9.234167 10.065446 -0.831279 \n",
"2025-08-26 11:00:00 1336.0 1394051.0 9.357401 9.562174 -0.204773 \n",
"2025-08-26 11:15:00 1332.0 1392975.0 8.889531 9.460414 -0.570883 \n",
"2025-08-26 13:30:00 1323.0 1398645.0 8.445054 12.341088 -3.896034 \n",
"2025-08-26 13:45:00 1324.0 1401787.0 11.483147 11.724033 -0.240886 \n",
"\n",
" activity \n",
"datetime \n",
"2025-08-26 09:15:00 21.914495 \n",
"2025-08-26 09:30:00 21.622120 \n",
"2025-08-26 09:45:00 21.189792 \n",
"2025-08-26 10:00:00 20.583584 \n",
"2025-08-26 10:30:00 19.755847 \n",
"2025-08-26 10:45:00 19.299613 \n",
"2025-08-26 11:00:00 18.919574 \n",
"2025-08-26 11:15:00 18.349944 \n",
"2025-08-26 13:30:00 20.786142 \n",
"2025-08-26 13:45:00 23.207180 \n"
]
},
{
"data": {
"text/plain": [
"<Figure size 2000x1500 with 4 Axes>"
],
"image/png": "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
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 11
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-17T15:39:44.423366Z",
"start_time": "2025-09-17T15:39:44.418223Z"
}
},
"cell_type": "code",
"source": "print(df_with_dp[df_with_dp['activity'] <= 0])",
"id": "81479ba5cf6e3f13",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" open high low close volume open_oi \\\n",
"datetime \n",
"2024-01-02 09:00:00 2009.0 2047.0 2000.0 2041.0 79791.0 449616.0 \n",
"\n",
" close_oi underlying_symbol dp_long dp_short dominance \\\n",
"datetime \n",
"2024-01-02 09:00:00 458070.0 CZCE.SA405 0.0 0.0 0.0 \n",
"\n",
" activity \n",
"datetime \n",
"2024-01-02 09:00:00 0.0 \n"
]
}
],
"execution_count": 18
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-09-17T03:17:59.035283Z",
"start_time": "2025-09-17T03:17:57.911292Z"
2025-09-16 09:59:38 +08:00
}
},
"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>"
],
2025-09-20 00:04:51 +08:00
"image/png": "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
2025-09-16 09:59:38 +08:00
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- Market Regime Statistics ---\n",
" Counts (Bars) Percentage (%)\n",
"regime \n",
2025-09-20 00:04:51 +08:00
"Choppy / Ranging 2362 25.96%\n",
"Strong Bear Trend 2084 22.90%\n",
"Strong Bull Trend 1846 20.29%\n",
"Transition 1696 18.64%\n",
"Low Volatility Squeeze 1112 12.22%\n",
2025-09-16 09:59:38 +08:00
"\n",
"==============================\n",
"Analysis Summary:\n",
2025-09-20 00:04:51 +08:00
"- Strong Trend (Bull+Bear) constitutes 43.19% of the time.\n",
"- The market is in a Choppy / Ranging state for 25.96% of the time.\n",
"- Low Volatility Squeeze, often preceding major moves, occurs 12.22% of the time.\n",
"- The remaining 18.64% is in a transitional state.\n",
2025-09-16 09:59:38 +08:00
"==============================\n"
]
}
],
2025-09-20 00:04:51 +08:00
"execution_count": 12
2025-09-16 09:59:38 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-09-20 00:04:51 +08:00
"end_time": "2025-09-17T03:17:59.918050Z",
"start_time": "2025-09-17T03:17:59.065884Z"
2025-09-16 09:59:38 +08:00
}
},
"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>"
],
2025-09-20 00:04:51 +08:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAAB8AAAAYxCAYAAADG+YYCAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XdcE+cfB/BPwh4qoogLt+IAt9at4LbuXS1qnbVaRx11VbHWurdWrXuPKqJ1b+tEQUScKIoDFZG9SUh+f/DLNSEhA8LQft6vF68XSS53z90999zlvvd8H5FcLpeDiIiIiIiIiIiIiIiIiIjoMyfO6wIQEREREREREREREREREREZAwPgRERERERERERERERERET0RWAAnIiIiIiIiIiIiIiIiIiIvggMgBMRERERERERERERERER0ReBAXAiIiIiIiIiIiIiIiIiIvoiMABORERERERERERERERERERfBAbAiYiIiIiIiIiIiIiIiIjoi8AAOBERERERERERERERERERfREYACciIiIiIiIiIiIiIiIioi8CA+BERERERHnA3d0dzs7OcHZ2xtu3b/O6OET0H7VmzRqhLVqzZo1R5unl5SXMc9q0aUaZJ/0rJ88fMpkMPXv2hLOzM7p16waZTGbU+WvD8yIR5bRp06YJ7YyXl5fGaSZOnAhnZ2c0btwYsbGxuVxCIiIiMhbTvC4AERER6c/DwwO3b98GAIwdOxY//vhjHpfoy7BmzRqsXbvWqPO8cOECSpcubdR5fok0bfvBgwdjxowZes/j8uXLGDVqlMp7DRs2xK5du4xSRso5wcHBOHbsGO7evYuXL18iNjYWMpkM1tbWKFasGJycnFCjRg3UqVMH9evXh4WFRV4Xmb5QyufXjMzNzVGgQAHY2tqiSJEiqF69OmrUqIFGjRqhZMmSuVxSopx14MABPHz4EAAwefJkiMXa+02kpqbi7NmzuHTpEh49eoRPnz4hISEBFhYWKFiwIEqWLInKlSvD1dUVjRo1gpOTU26sBilJTU3F0aNHcfr0aTx+/BgxMTEwNzdHiRIlUK1aNTRu3Bht27ZFoUKFcqwMzs7OmX4mFothY2MDe3t7VK1aFc2bN0enTp1gY2OTY+WJj4/HiRMncP36dTx+/BiRkZFISkqChYUFChcujFKlSsHZ2Rmurq5o3LgxihUrlmNlofxnwoQJOHv2LCIjI7F69WrMmjUrr4tEREREWcAAOBERERHlKydOnMDUqVNhaqrfpeqRI0dyuET/LT4+Phg0aBCAnHuQIDY2FvPnz4e3t7fGz2NiYhATE4Nnz57h4sWLAABLS0ucPn0aJUqUUJvey8sL06dPBwD06NEDCxcuNHqZPwdv375F69atAQClSpUSth1lT2pqKiIiIhAREYFXr17h7t27ANKDNs2bN4eHhweaN2+ex6Ukyr6EhASsXr0aAFC7dm2d9frSpUuYM2cOwsLC1D5LTExEYmIiPnz4gLt37+LAgQMAgG+//Ra//PKL8QtPGr158wZjxozB06dPVd6XSqUIDg5GcHAwjh8/jrlz5+LmzZuwtbXN9TLKZDLExcUhLi4Or169wpkzZ7Bs2TLMmzcPbdu2NfryDh06hEWLFmns2auot6GhoSoPRU2dOhXDhg0zelkofypbtiw6d+4Mb29v7Nu3D4MGDUKZMmXyulhERERkIAbAiYiI6D+vZs2aGDhwoNZpvL29kZCQAABo3LgxKlSooHX6vLiB+KX49OkTrl27hlatWumcNjY2FpcuXcr5QpHRxMTEYPDgwXj8+LHwnrW1NVxcXFC6dGmYmZkJN8GDgoIgkUgAAMnJyUhNTc2rYtN/iKurK2rWrCm8Vg7OPH/+HKGhocL7V65cwZUrV9CzZ0/MnDmTbT991nbu3InIyEgAwIgRI7ROe/jwYcycORNyuVx4r1y5cnB2doadnZ3w4Mjjx48RHh4uTBMTE5MzhSc1CQkJGDZsGF69eiW8V6lSJVStWhVmZmZ4+fIlHjx4AKlUitTU1FxLd9+mTRs4OjoKr2UyGaKionDv3j18+PABABAVFYVx48Zh9erVRg2Ca8o8VKVKFVSqVAkFChRAcnIywsPD8ejRI0RHRwvTMA32f8+IESPg7e0NqVSK1atXY+nSpXldJCIiIjIQA+BERET0n9eyZUu0bNlS6zSXL18WAuBdu3ZFz549c6No/ymVKlXC8+fPAaQ/cKBPAPzUqVNISUlR+z7lXwsXLhSC32ZmZvjpp5/wzTffwMrKSm3a5ORkXLt2DSdPnsTZs2dzu6j0H9WyZUutQ4yEh4fj6NGj2LVrlxCs8fLywrNnz7B7925YWlrmVlGJjCYlJQU7d+4EAJQsWRLu7u6ZTvvq1St4enoKwe86depg9uzZqF69usbpX758iXPnzuHQoUPGLzhlavv27ULw29LSEgsWLECnTp1UpomKisKxY8ewffv2XCvXoEGD8NVXX6m9L5PJcOTIEXh6egoBeU9PTzRr1kzjNYKh7ty5oxL8dnNzw/Tp01G2bFmN0z969Ahnz57F4cOHs71s+vxUqlQJDRs2xO3bt3Hy5ElMnDgRpUqVyutiERERkQG0D+ZERERERJRLqlSpgqpVqwIALl68iLi4OJ3fUaQ/NzMzw9dff52j5aPs+/Tpk0ra8/nz52Po0KGZ3ti2tLREmzZtsHz5cly6dAkODg65VFKizDk4OGD48OE4efIkOnToILwfGBiIadOm5WHJiLLu2LFjQu/vnj17ah37e/v27UJGjsqVK2PHjh2ZBr8BoHz58hg5ciTOnDmD8ePHG7fglKkzZ84I/48ZM0Yt+A0AhQsXxuDBg3HmzJk8z2AhFovRq1cvTJkyRXjv06dPOH/+vFHmv2nTJuH/pk2b4o8//sg0+A0A1atXx4QJE3Dp0iX07dvXKGWgz0vv3r0BAGlpadi9e3cel4aIiIgMxQA4EREREeUbPXr0AJDeE+3UqVNap339+jX8/f0BAC1atEDhwoVzvHyUPTdu3BBSrDo4OKBr1656f9fBwQHW1tY5VTQig9nY2GDlypUq2SpOnTqFO3fu5F2hiLJIuZerpkCpsuvXrwv/f/vtt7CwsNBrGSKRCE5OTlkrIBns9evXwv/NmjXTOq25ubnWhx5yU//+/VXqlDHaVJlMhps3bwqvv/vuO73X19TUlD1//6Nat24Nc3NzAMDRo0chlUrzuERERERkCKZAJyIi+o+7evUqTp48ibt37yI8PBxSqRRFihRB9erV0bp1a3Tp0gVmZmZa5zFt2jShJ+6CBQvQs2dPJCUlwcvLC8ePH8erV68QGxuLIkWKoF69ehg4cCDq1aundxmDg4Oxb98+XL16FWFhYTA3N0eJEiXg5uaGfv36oUSJEvDx8cGgQYMAAA0bNsSuXbuyvlGMQC6X4/z58zh//jzu3buHT58+ITU1Ffb29qhRowbatm2LLl26wNQ088uxt2/fonXr1gCAUqVK4eLFiwAAX19fHDlyBH5+fggPD0d8fDwGDRqEmTNnAgCcnZ2FeTx9+hQA8PjxY+zfvx+3bt3Cx48fAaSn9uvevTv69eunVo7AwEDs2bMHAQEBeP/+PSwsLFClShX06dPHoKCloTp37owlS5ZAKpXC29tba48b5Z7E3bt3R0REhN7LCQ0NxZUrV+Dr64ugoCC8f/8eycnJsLW1RbFixVC3bl306NEDtWvX1jkvTfU/NjYWR44cwdmzZ/H69WtEREQgLS0Nd+7cQcGCBfUup8Kff/6JZcuWAQBMTEzw22+/aUzDn5iYCG9vb/zzzz94+vQpIiMjIRaL4eDggHr16qFr165o3LixxmVoGhfz9u3bKvVJQbk+GiIsLEz4v2TJkhCJRAbPQ5nytlc4cuSI2nuAerugvL5jx47Fjz/+iOTkZPz99984deoUXrx4gU+fPkEikcDb2xvVqlUDALi7uwtjQF+4cAGlS5fWu4yK+qHLlStXcOHCBaFdjo+Ph5WVFZycnODq6ioM26A4br28vDB9+nSVeYSGhmrcd8C/7ULG7/bo0QMLFy7UWrbM2iV9ptHVdil7//49vLy
2025-09-16 09:59:38 +08:00
},
"metadata": {},
"output_type": "display_data"
}
],
2025-09-20 00:04:51 +08:00
"execution_count": 13
2025-09-16 09:59:38 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-09-20 00:04:51 +08:00
"end_time": "2025-09-17T03:18:00.735463Z",
"start_time": "2025-09-17T03:17:59.945991Z"
2025-09-16 09:59:38 +08:00
}
},
"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>"
],
2025-09-20 00:04:51 +08:00
"image/png": "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
2025-09-16 09:59:38 +08:00
},
"metadata": {},
"output_type": "display_data"
}
],
2025-09-20 00:04:51 +08:00
"execution_count": 14
2025-09-16 09:59:38 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-09-20 00:04:51 +08:00
"end_time": "2025-09-17T03:18:01.243361Z",
"start_time": "2025-09-17T03:18:00.755372Z"
2025-09-16 09:59:38 +08:00
}
},
"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>"
],
2025-09-20 00:04:51 +08:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAAB8AAAASmCAYAAABRBbHQAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd0FFUbBvBnS3qjBEJvAUJJAqGFKr1KDdUCCCo2EBSQIAiCKE1EAcVGFQSU3lWQTkhI6B0CEQhJSG+bZLPl+yPuuJvt6Zvv+Z3DIbs7O3Nn5s6du/PeIlKr1WoQERERERERERERERERERHZOHFpJ4CIiIiIiIiIiIiIiIiIiKgoMABORERERERERERERERERETlAgPgRERERERERERERERERERULjAATkRERERERERERERERERE5QID4EREREREREREREREREREVC4wAE5EREREREREREREREREROUCA+BERERERERERERERERERFQuMABORERERERERERERERERETlAgPgRERERERERERERERERERULjAATkREZCOCg4Ph4+MDHx8f7N69u7STQ0REJWz37t3CfSA4OLi0k0NE+YwdO1a4RkNDQ0s7OUQmhYaGCvl17NixBpd5+vSpsEyPHj1KOIVExYv1KiIiovJNWtoJICIiMmXs2LEICwsz+Jm9vT3c3Nzg6uqKypUro1mzZmjevDnat2+PGjVqlHBKbU9wcDD27Nmj897HH3+M8ePHW7yOrVu3YuHChTrvDRs2DEuWLCmSNBKVJlPljymbN29GYGBgMaSIyDb4+PjovLazs8Pp06dRqVIli9cxfPhw3LhxQ+c9Xlul59q1azh37hxCQ0MRExOD5ORkZGZmwsXFBe7u7vD29kbTpk3RuXNntGrVCmIx29qTbVm9ejXWrFmj89748ePx8ccfW7yOkydP4q233tJ5r127dvjll1+KJI1UNhjKK4V1/Phx1KpVq0jXWV706NED0dHRRbY+XpNERET/P/irlIiIbJZcLkdiYiL++ecfXLp0CVu2bMHs2bPRs2dPTJo0CWfOnCntJNqcffv2WbV8/gA6lU2W9PAhIiouubm5OHTokMXLP3jwQC/4TaXj3LlzGDNmDEaOHImvv/4aISEhiIqKQmpqKhQKBVJTU/HkyROcPHkSa9euxSuvvIIXXngB3333HTIy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
2025-09-16 09:59:38 +08:00
},
"metadata": {},
"output_type": "display_data"
}
],
2025-09-20 00:04:51 +08:00
"execution_count": 15
2025-09-16 09:59:38 +08:00
}
],
"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
}