Files
NewQuant/data/ analysis/Trend.ipynb

704 lines
126 KiB
Plaintext
Raw Normal View History

2025-09-16 09:59:38 +08:00
{
"cells": [
{
"cell_type": "code",
"id": "initial_id",
"metadata": {
"collapsed": true,
"ExecuteTime": {
2025-12-16 00:36:36 +08:00
"end_time": "2025-12-08T15:18:02.157580Z",
"start_time": "2025-12-08T15:18:02.151197Z"
2025-09-16 09:59:38 +08:00
}
},
"source": [
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
2025-11-07 16:26:00 +08:00
"\n",
"import pandas as pd\n",
2025-09-16 09:59:38 +08:00
"\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",
2025-12-16 00:36:36 +08:00
"file_path = 'D:/PyProject/NewQuant/data/data/KQ_m@SHFE_hc/KQ_m@SHFE_hc_min15.csv'\n",
2025-09-16 09:59:38 +08:00
"\n",
"sns.set(style='whitegrid')\n",
"plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签\n",
"plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号\n"
],
"outputs": [],
2025-12-16 00:36:36 +08:00
"execution_count": 42
2025-09-16 09:59:38 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-12-16 00:36:36 +08:00
"end_time": "2025-12-08T15:18:02.203253Z",
"start_time": "2025-12-08T15:18:02.157580Z"
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",
"print(df_raw)\n",
"# df_df_raw = df_df_raw[df_df_raw.index >= '2024-01-01']"
2025-09-16 09:59:38 +08:00
],
"id": "1638e05ca7ef1ac8",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
2025-12-16 00:36:36 +08:00
"Successfully loaded 26087 rows of data.\n",
2025-09-16 09:59:38 +08:00
"First 5 rows of data:\n",
2025-12-16 00:36:36 +08:00
" open high low close volume open_oi \\\n",
"datetime \n",
"2020-12-31 14:45:00 4534.0 4569.0 4528.0 4554.0 50433.0 526437.0 \n",
"2021-01-04 09:00:00 4507.0 4510.0 4451.0 4468.0 149169.0 523892.0 \n",
"2021-01-04 09:15:00 4468.0 4473.0 4423.0 4437.0 75329.0 513989.0 \n",
"2021-01-04 09:30:00 4437.0 4460.0 4435.0 4447.0 47021.0 514326.0 \n",
"2021-01-04 09:45:00 4447.0 4474.0 4446.0 4473.0 28511.0 509735.0 \n",
2025-09-24 23:14:14 +08:00
"\n",
2025-12-16 00:36:36 +08:00
" close_oi underlying_symbol \n",
"datetime \n",
"2020-12-31 14:45:00 523892.0 SHFE.hc2105 \n",
"2021-01-04 09:00:00 513989.0 SHFE.hc2105 \n",
"2021-01-04 09:15:00 514326.0 SHFE.hc2105 \n",
"2021-01-04 09:30:00 509735.0 SHFE.hc2105 \n",
"2021-01-04 09:45:00 509228.0 SHFE.hc2105 \n",
" open high low close volume open_oi \\\n",
"datetime \n",
2025-12-16 00:36:36 +08:00
"2020-12-31 14:45:00 4534.0 4569.0 4528.0 4554.0 50433.0 526437.0 \n",
"2021-01-04 09:00:00 4507.0 4510.0 4451.0 4468.0 149169.0 523892.0 \n",
"2021-01-04 09:15:00 4468.0 4473.0 4423.0 4437.0 75329.0 513989.0 \n",
"2021-01-04 09:30:00 4437.0 4460.0 4435.0 4447.0 47021.0 514326.0 \n",
"2021-01-04 09:45:00 4447.0 4474.0 4446.0 4473.0 28511.0 509735.0 \n",
"... ... ... ... ... ... ... \n",
2025-12-16 00:36:36 +08:00
"2025-09-19 21:30:00 3391.0 3393.0 3387.0 3389.0 15546.0 1406485.0 \n",
"2025-09-19 21:45:00 3389.0 3392.0 3386.0 3388.0 11803.0 1406923.0 \n",
"2025-09-19 22:00:00 3388.0 3391.0 3387.0 3387.0 7365.0 1406315.0 \n",
"2025-09-19 22:15:00 3387.0 3390.0 3385.0 3389.0 10361.0 1405811.0 \n",
"2025-09-19 22:30:00 3389.0 3390.0 3385.0 3387.0 10758.0 1404506.0 \n",
"\n",
" close_oi underlying_symbol \n",
"datetime \n",
2025-12-16 00:36:36 +08:00
"2020-12-31 14:45:00 523892.0 SHFE.hc2105 \n",
"2021-01-04 09:00:00 513989.0 SHFE.hc2105 \n",
"2021-01-04 09:15:00 514326.0 SHFE.hc2105 \n",
"2021-01-04 09:30:00 509735.0 SHFE.hc2105 \n",
"2021-01-04 09:45:00 509228.0 SHFE.hc2105 \n",
"... ... ... \n",
2025-12-16 00:36:36 +08:00
"2025-09-19 21:30:00 1406923.0 SHFE.hc2601 \n",
"2025-09-19 21:45:00 1406315.0 SHFE.hc2601 \n",
"2025-09-19 22:00:00 1405811.0 SHFE.hc2601 \n",
"2025-09-19 22:15:00 1404506.0 SHFE.hc2601 \n",
"2025-09-19 22:30:00 1402724.0 SHFE.hc2601 \n",
"\n",
2025-12-16 00:36:36 +08:00
"[26087 rows x 8 columns]\n"
2025-09-24 23:14:14 +08:00
]
2025-09-20 00:04:51 +08:00
}
],
2025-12-16 00:36:36 +08:00
"execution_count": 43
2025-09-20 00:04:51 +08:00
},
{
"metadata": {
"ExecuteTime": {
2025-12-16 00:36:36 +08:00
"end_time": "2025-12-08T15:18:02.224837Z",
"start_time": "2025-12-08T15:18:02.203253Z"
2025-09-20 00:04:51 +08:00
}
},
"cell_type": "code",
2025-09-24 23:14:14 +08:00
"source": [
"import numpy as np\n",
"import pandas as pd\n",
2025-12-16 00:36:36 +08:00
"import talib\n",
"from scipy.signal import stft\n",
2025-09-24 23:14:14 +08:00
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"from tqdm import tqdm\n",
2025-09-24 23:14:14 +08:00
"\n",
2025-12-16 00:36:36 +08:00
"class UniversalAnalyzer:\n",
" def __init__(self, df: pd.DataFrame, bars_per_day: int = 23):\n",
" \"\"\"\n",
2025-12-16 00:36:36 +08:00
" :param df: 包含 ['high', 'low', 'close'] 的原始数据\n",
" :param bars_per_day: 每天的K线数 (如小时线=23, 15分钟=96)\n",
" \"\"\"\n",
2025-12-16 00:36:36 +08:00
" self.df = df.reset_index(drop=True)\n",
" self.bars_per_day = bars_per_day\n",
2025-12-16 00:36:36 +08:00
" self.high_freq_days = 1.0 # 固定物理常数:过滤日内噪音\n",
" print(f\"--- 分析器初始化: {len(df)} Bars, {bars_per_day} Bars/Day ---\")\n",
"\n",
2025-12-16 00:36:36 +08:00
" # ================= 工具函数STFT 计算核心 =================\n",
" def _calc_stft_signal(self, prices, window_days):\n",
" window_len = int(window_days * self.bars_per_day)\n",
" if window_len % 2 != 0: window_len += 1\n",
"\n",
2025-12-16 00:36:36 +08:00
" # 极短数据保护\n",
" if len(prices) < window_len: return np.zeros(len(prices))\n",
"\n",
" # 1. 滚动计算方差进行标准化 (简单起见这里用全量标准化严谨可用Rolling)\n",
" # 为了速度和形态分析,全量标准化是可以接受的近似\n",
" norm = (prices - np.mean(prices)) / (np.std(prices) + 1e-9)\n",
"\n",
" try:\n",
2025-12-16 00:36:36 +08:00
" f, t, Zxx = stft(norm, fs=self.bars_per_day, nperseg=window_len,\n",
" noverlap=max(0, window_len // 2), padded=False)\n",
" except:\n",
2025-12-16 00:36:36 +08:00
" return np.zeros(len(prices))\n",
"\n",
2025-12-16 00:36:36 +08:00
" # 提取当前时间步的能量 (Zxx最后一维是时间我们需要对齐)\n",
" # STFT 输出时间轴通常比原数据短,这里我们需要做对齐处理\n",
" # 为简化分析,我们使用逐点滑窗计算的模拟函数\n",
" return None # 占位,实际调用下面的 _generate_full_series\n",
"\n",
2025-12-16 00:36:36 +08:00
" def _generate_full_series(self, window_days):\n",
" \"\"\"生成全历史 STFT 强度序列\"\"\"\n",
" window_len = int(window_days * self.bars_per_day)\n",
" if window_len % 2 != 0: window_len += 1\n",
"\n",
2025-12-16 00:36:36 +08:00
" closes = self.df['close'].values\n",
" n = len(closes)\n",
" signals = np.zeros(n)\n",
"\n",
" # 步长设为1保证精度设为5-10可加速\n",
" step = 1\n",
"\n",
2025-12-16 00:36:36 +08:00
" # 使用 stride_tricks 进行超高速计算 (替代循环)\n",
" # 注意:这里计算的是每一个窗口的“瞬时”状态\n",
"\n",
2025-12-16 00:36:36 +08:00
" print(f\"正在计算 Window={window_days} 的信号序列...\")\n",
" for i in range(window_len, n, step):\n",
" segment = closes[i-window_len:i]\n",
" std_val = np.std(segment)\n",
" if std_val < 1e-9: continue\n",
"\n",
" norm = (segment - np.mean(segment)) / std_val\n",
"\n",
" try:\n",
" f, t, Zxx = stft(norm, fs=self.bars_per_day, nperseg=window_len,\n",
" noverlap=max(0, window_len // 2), padded=False)\n",
"\n",
" # 只取最后一列频谱\n",
" spec = np.abs(Zxx[:, -1]) ** 2\n",
" freqs = f\n",
"\n",
" # 频率绑定\n",
" low_limit = (1.0 / window_days) + 1e-5 # 加上偏移量防止丢失基频\n",
"\n",
" low_mask = freqs <= low_limit\n",
" high_mask = freqs > self.high_freq_days\n",
"\n",
" low_e = np.sum(spec[low_mask])\n",
" high_e = np.sum(spec[high_mask])\n",
"\n",
" signals[i] = low_e / (low_e + high_e + 1e-9)\n",
" except:\n",
" continue\n",
"\n",
" return signals\n",
"\n",
" # ================= 模块 1: 确定止损 (物理常数) =================\n",
" def step1_analyze_stop_loss(self):\n",
" print(\"\\n=== [Step 1] 止损参数标定 ===\")\n",
" high = self.df['high'].values\n",
" low = self.df['low'].values\n",
" close = self.df['close'].values\n",
"\n",
" atr = talib.ATR(high, low, close, timeperiod=14)\n",
" tr = talib.TRANGE(high, low, close)\n",
"\n",
" # 过滤无效数据\n",
" mask = (atr > 0) & (~np.isnan(atr))\n",
" ratios = tr[mask] / atr[mask]\n",
"\n",
" p99 = np.percentile(ratios, 99)\n",
" p99_7 = np.percentile(ratios, 99.7) # 3倍标准差塔勒布极限\n",
"\n",
" print(f\"建议止损倍数 (基于 P99.7): {p99_7:.2f}\")\n",
" suggested = np.ceil(p99_7 * 2) / 2 # 向上取整到0.5\n",
" print(f\"-> 最终推荐 Stop Loss ATR Multiplier: {suggested}\")\n",
" return suggested\n",
"\n",
" # ================= 模块 2: 确定窗口 (信噪比) =================\n",
" def step2_select_window(self, windows=[2.0, 3.0, 4.0, 5.0, 6.0]):\n",
" print(\"\\n=== [Step 2] 最佳窗口扫描 ===\")\n",
" best_w = None\n",
" max_std = -1\n",
"\n",
" stats = []\n",
" for w in windows:\n",
" sigs = self._generate_full_series(w)\n",
" # 去除0值(预热期)\n",
" sigs = sigs[sigs > 0]\n",
"\n",
" if len(sigs) == 0: continue\n",
"\n",
" std_val = np.std(sigs)\n",
" stats.append((w, std_val))\n",
" print(f\"Window {w}: StdDev = {std_val:.4f}\")\n",
"\n",
" if std_val > max_std:\n",
" max_std = std_val\n",
" best_w = w\n",
"\n",
" print(f\"-> 最终推荐 Spectral Window Days: {best_w} (区分度最高)\")\n",
" return best_w\n",
"\n",
" # ================= 模块 3: 确定阈值 (分布+收益) =================\n",
" def step3_calibrate_thresholds(self, best_window):\n",
" print(\"\\n=== [Step 3] 阈值校准 (分布 & IC分析) ===\")\n",
" # 1. 生成信号\n",
" signals = self._generate_full_series(best_window)\n",
" closes = self.df['close'].values\n",
"\n",
" # 2. 基础分布统计 (PDF) -> 确定 Exit\n",
" valid_sigs = signals[signals > 0]\n",
" p50 = np.median(valid_sigs)\n",
" print(f\"分布中位数 (建议 Exit Threshold): {p50:.2f}\")\n",
"\n",
" # 3. 收益分组分析 (Bucketing) -> 确定 Entry\n",
" # 计算未来收益 (持有窗口长度)\n",
" hold_period = int(best_window * self.bars_per_day)\n",
" fwd_ret = pd.Series(closes).pct_change(hold_period).shift(-hold_period)\n",
"\n",
" # 确定方向: 简单用过去 hold_period 的涨跌作为方向过滤器\n",
" # 假设: Signal强 且 过去在涨 -> 做多; Signal强 且 过去在跌 -> 做空\n",
" past_ret = pd.Series(closes).pct_change(hold_period).fillna(0)\n",
" direction = np.sign(past_ret)\n",
"\n",
" # 策略收益 = 方向 * 未来收益 (假设做对方向)\n",
" # 我们只关心 Signal 强度是否能放大收益\n",
" df_anl = pd.DataFrame({\n",
" 'signal': signals,\n",
" 'fwd_ret': fwd_ret,\n",
" 'direction': direction\n",
" }).dropna()\n",
"\n",
" df_anl = df_anl[df_anl['signal'] > 0]\n",
"\n",
" # 核心假设: 我们在信号强时顺势交易\n",
" df_anl['strat_ret'] = np.sign(df_anl['direction']) * df_anl['fwd_ret']\n",
"\n",
" # 分桶\n",
" df_anl['bucket'] = pd.cut(df_anl['signal'], bins=np.linspace(0, 1, 21))\n",
" grouped = df_anl.groupby('bucket', observed=False)['strat_ret'].mean()\n",
" counts = df_anl.groupby('bucket', observed=False)['strat_ret'].count()\n",
"\n",
" # 绘图\n",
" fig, ax1 = plt.subplots(figsize=(12, 5))\n",
" colors = ['red' if v > 0 else 'green' for v in grouped.values]\n",
" grouped.plot(kind='bar', ax=ax1, color=colors, alpha=0.7)\n",
" ax1.set_title(f\"Signal Strength vs Profitability (Window={best_window})\")\n",
" ax1.set_ylabel(\"Avg Future Return\")\n",
"\n",
" ax2 = ax1.twinx()\n",
" counts.plot(kind='line', ax=ax2, color='blue', marker='o')\n",
" plt.show()\n",
"\n",
2025-12-16 00:36:36 +08:00
" print(\"请观察图表:\")\n",
" print(\"1. 寻找红色柱子稳定出现且较高的区域起始点 -> Entry Threshold\")\n",
" print(\"2. 避开深绿色柱子区域 (陷阱区)\")\n",
"\n",
2025-12-16 00:36:36 +08:00
" return grouped\n",
"\n",
2025-12-16 00:36:36 +08:00
" # ================= 模块 4: 热力图验证 (鲁棒性) =================\n",
" def step4_heatmap_validation(self, center_window, center_entry, stop_loss_mult):\n",
" print(\"\\n=== [Step 4] 热力图鲁棒性验证 ===\")\n",
"\n",
2025-12-16 00:36:36 +08:00
" # 构建搜索网格 (在中心点附近波动)\n",
" windows = [center_window - 1.0, center_window - 0.5, center_window, center_window + 0.5, center_window + 1.0]\n",
" # 过滤掉不合理的窗口\n",
" windows = [w for w in windows if w >= 2.0]\n",
2025-11-07 16:26:00 +08:00
"\n",
2025-12-16 00:36:36 +08:00
" entries = np.arange(max(0.6, center_entry - 0.15), min(0.95, center_entry + 0.15), 0.05)\n",
"\n",
" results = pd.DataFrame(index=np.round(entries, 2), columns=windows)\n",
"\n",
" # 这里使用快速回测逻辑 (简化版)\n",
" for w in tqdm(windows):\n",
" signals = self._generate_full_series(w)\n",
" closes = self.df['close'].values\n",
" atr = talib.ATR(self.df['high'], self.df['low'], closes, timeperiod=14)\n",
" atr = np.nan_to_num(atr, nan=np.mean(closes)*0.01)\n",
"\n",
2025-12-16 00:36:36 +08:00
" for e in entries:\n",
" # 固定 Exit 为 0.45 (或基于 P50 的动态值,这里简化固定)\n",
" calmar = self._fast_backtest_calmar(\n",
" signals, closes, atr,\n",
" entry=e, exit=0.45, sl=stop_loss_mult\n",
" )\n",
" results.loc[np.round(e, 2), w] = calmar\n",
2025-11-07 16:26:00 +08:00
"\n",
2025-12-16 00:36:36 +08:00
" plt.figure(figsize=(10, 6))\n",
" sns.heatmap(results.astype(float), annot=True, cmap=\"RdYlGn\", fmt=\".2f\")\n",
" plt.title(f\"Robustness Heatmap (SL={stop_loss_mult})\")\n",
" plt.gca().invert_yaxis()\n",
" plt.show()\n",
2025-11-07 16:26:00 +08:00
"\n",
2025-12-16 00:36:36 +08:00
" def _fast_backtest_calmar(self, signals, closes, atr, entry, exit, sl):\n",
" # 极简向量化回测计算 Calmar\n",
" # 仅供参数敏感度分析,非精确回测\n",
" pos = np.zeros(len(closes))\n",
" # 简单的状态机模拟\n",
" curr_pos = 0\n",
" entry_px = 0\n",
"\n",
" # 预计算方向 (动量)\n",
" mom = np.sign(pd.Series(closes).pct_change(5).fillna(0).values)\n",
"\n",
" equity = [1.0]\n",
" for i in range(1, len(closes)):\n",
" px = closes[i]\n",
" sig = signals[i-1]\n",
" my_atr = atr[i-1]\n",
"\n",
" pnl = 0\n",
" if curr_pos == 0:\n",
" if sig > entry:\n",
" curr_pos = mom[i-1] # 顺势\n",
" entry_px = px\n",
" elif curr_pos != 0:\n",
" # 止损\n",
" if curr_pos == 1 and px < (entry_px - sl * my_atr): curr_pos = 0\n",
" elif curr_pos == -1 and px > (entry_px + sl * my_atr): curr_pos = 0\n",
" # 离场\n",
" elif sig < exit: curr_pos = 0\n",
"\n",
" # 计算 PnL\n",
" if curr_pos == 1: pnl = (px - closes[i-1]) / closes[i-1]\n",
" elif curr_pos == -1: pnl = (closes[i-1] - px) / closes[i-1]\n",
"\n",
" equity.append(equity[-1] * (1 + pnl))\n",
"\n",
" eq_series = pd.Series(equity)\n",
" dd = (eq_series / eq_series.cummax() - 1).min()\n",
" if dd == 0: return 0\n",
" ann_ret = (eq_series.iloc[-1] ** (252*self.bars_per_day/len(eq_series))) - 1\n",
" return ann_ret / abs(dd)"
],
"id": "b98755d3f0552e8b",
"outputs": [],
"execution_count": 44
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-12-08T15:18:02.237244Z",
"start_time": "2025-12-08T15:18:02.227739Z"
}
},
"cell_type": "code",
"source": [
"# 假设是15分钟线一天有 4小时(夜盘)+5.5小时(日盘) = 9.5小时 ≈ 38根bar?\n",
"# 或者按照实际交易所 bar 数填写\n",
"analyzer = UniversalAnalyzer(df_raw, bars_per_day=23)"
],
"id": "57962b7585161e57",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- 分析器初始化: 26087 Bars, 23 Bars/Day ---\n"
]
}
],
"execution_count": 45
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-12-08T15:18:02.252250Z",
"start_time": "2025-12-08T15:18:02.246891Z"
}
},
"cell_type": "code",
"source": [
"# 这一步直接给你 StopLoss 参数\n",
"sl_mult = analyzer.step1_analyze_stop_loss()\n",
"# 记录这个值,例如 4.0"
],
"id": "5a4888719bd6ccf",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== [Step 1] 止损参数标定 ===\n",
"建议止损倍数 (基于 P99.7): 3.49\n",
"-> 最终推荐 Stop Loss ATR Multiplier: 3.5\n"
]
}
],
"execution_count": 46
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-12-08T15:18:12.422635Z",
"start_time": "2025-12-08T15:18:02.258765Z"
}
},
"cell_type": "code",
"source": [
"# 这一步通过打印的 StdDev 帮你找最敏感的窗口\n",
"best_window = analyzer.step2_select_window([2.0, 3.0, 4.0, 5.0, 6.0, 8.0])\n",
"# 记录这个值,例如 5.0 (StdDev最大)"
],
"id": "65c036e6236cff5",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== [Step 2] 最佳窗口扫描 ===\n",
"正在计算 Window=2.0 的信号序列...\n",
"Window 2.0: StdDev = 0.2481\n",
"正在计算 Window=3.0 的信号序列...\n",
"Window 3.0: StdDev = 0.2468\n",
"正在计算 Window=4.0 的信号序列...\n",
"Window 4.0: StdDev = 0.2326\n",
"正在计算 Window=5.0 的信号序列...\n",
"Window 5.0: StdDev = 0.2270\n",
"正在计算 Window=6.0 的信号序列...\n",
"Window 6.0: StdDev = 0.2172\n",
"正在计算 Window=8.0 的信号序列...\n",
"Window 8.0: StdDev = 0.2069\n",
"-> 最终推荐 Spectral Window Days: 2.0 (区分度最高)\n"
]
}
],
"execution_count": 47
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-12-08T15:18:14.229745Z",
"start_time": "2025-12-08T15:18:12.429188Z"
}
},
"cell_type": "code",
"source": [
"# 这一步会画出柱状图\n",
"# 1. 看控制台输出的 Median -> 这是你的 Exit Threshold (比如 0.42)\n",
"# 2. 看弹出的图表 -> 找到红色柱子开始稳定且高的位置 -> 这是你的 Entry Threshold (比如 0.85)\n",
"analyzer.step3_calibrate_thresholds(best_window)"
],
"id": "dce8f46ee6f5c5dd",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== [Step 3] 阈值校准 (分布 & IC分析) ===\n",
"正在计算 Window=2.0 的信号序列...\n",
"分布中位数 (建议 Exit Threshold): 0.71\n"
]
},
{
"data": {
"text/plain": [
"<Figure size 1200x500 with 2 Axes>"
],
"image/png": "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
},
"metadata": {},
"output_type": "display_data",
"jetTransient": {
"display_id": null
}
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"请观察图表:\n",
"1. 寻找红色柱子稳定出现且较高的区域起始点 -> Entry Threshold\n",
"2. 避开深绿色柱子区域 (陷阱区)\n"
]
},
{
"data": {
"text/plain": [
"bucket\n",
"(0.0, 0.05] -0.000708\n",
"(0.05, 0.1] -0.000032\n",
"(0.1, 0.15] 0.001019\n",
"(0.15, 0.2] -0.000725\n",
"(0.2, 0.25] 0.001073\n",
"(0.25, 0.3] 0.000758\n",
"(0.3, 0.35] 0.001053\n",
"(0.35, 0.4] -0.000009\n",
"(0.4, 0.45] 0.000594\n",
"(0.45, 0.5] 0.000253\n",
"(0.5, 0.55] -0.000740\n",
"(0.55, 0.6] 0.001504\n",
"(0.6, 0.65] -0.000038\n",
"(0.65, 0.7] -0.000405\n",
"(0.7, 0.75] -0.000288\n",
"(0.75, 0.8] -0.000446\n",
"(0.8, 0.85] 0.000360\n",
"(0.85, 0.9] 0.000232\n",
"(0.9, 0.95] 0.000909\n",
"(0.95, 1.0] -0.000822\n",
"Name: strat_ret, dtype: float64"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 48
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-12-08T15:18:19.894590Z",
"start_time": "2025-12-08T15:18:14.240383Z"
}
},
"cell_type": "code",
"source": [
"# 把上面找到的参数填进去,跑热力图\n",
"# center_window = 5.0, center_entry = 0.85, sl = 4.0\n",
"analyzer.step4_heatmap_validation(best_window, 0.85, 4.0)"
2025-09-24 23:14:14 +08:00
],
2025-12-16 00:36:36 +08:00
"id": "1fd393d531324435",
2025-09-20 00:04:51 +08:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
2025-12-16 00:36:36 +08:00
"\n",
"=== [Step 4] 热力图鲁棒性验证 ===\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 0%| | 0/3 [00:00<?, ?it/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"正在计算 Window=2.0 的信号序列...\n"
2025-09-24 23:14:14 +08:00
]
},
{
"name": "stderr",
2025-09-24 23:14:14 +08:00
"output_type": "stream",
"text": [
2025-12-16 00:36:36 +08:00
" 33%|███▎ | 1/3 [00:01<00:03, 1.77s/it]"
2025-09-24 23:14:14 +08:00
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
2025-12-16 00:36:36 +08:00
"正在计算 Window=2.5 的信号序列...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 67%|██████▋ | 2/3 [00:03<00:01, 1.81s/it]"
2025-09-24 23:14:14 +08:00
]
},
2025-09-16 09:59:38 +08:00
{
2025-12-16 00:36:36 +08:00
"name": "stdout",
"output_type": "stream",
"text": [
"正在计算 Window=3.0 的信号序列...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 3/3 [00:05<00:00, 1.83s/it]\n"
2025-09-24 23:14:14 +08:00
]
},
2025-09-16 09:59:38 +08:00
{
"data": {
"text/plain": [
2025-12-16 00:36:36 +08:00
"<Figure size 1000x600 with 2 Axes>"
2025-09-16 09:59:38 +08:00
],
2025-12-16 00:36:36 +08:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAvsAAAIQCAYAAAAfAqEPAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAZdVJREFUeJzt3Qd0VNX2x/Ffeg+E3kPvIIgUQUFALPAERAT1iWKnib1gQ0AfD/VhR1QU7KhYEARBwC4g0qV3KQKBENJ7/usc/gkJE9LIkOTy/aw1i8yZuXfOTBKy77777OuRkZGRIQAAAACO41nSEwAAAADgHgT7AAAAgEMR7AMAAAAORbAPAAAAOBTBPgAAAOBQBPsAAACAQxHsAwAAAA5FsA8AAAA4FME+AKDAjh8/rkOHDjnqE9u2bVtJTwEA3IZgHwBQICkpKbr77rv11VdfOeYT+/vvvzVkyBBt2rSppKcCAG5BsA+UcsuXL1eTJk2ybh07dtTw4cO1Y8eOAu/j1Vdf1b/+9S+3ztMpzGf8zjvv5BgzweCIESN0rps8ebKCgoJ05513Zo39/vvvGjBggFq3bq3LL79c3333XY6fuwsuuKBY5zBjxgz16NGjUNukp6frpZdeUpcuXXTxxRfr/fffz3qsTp06evrppzVy5EglJCQU61wBoDQg2AfKiEmTJunzzz/X2LFjtXv3bv373/+2JRWlzZdffmkPUJA/Ewzv27evTHxU27dv1+zZs/Xss8/K0/PEn44tW7bYwP+SSy7R22+/rfPOO08PPPCA9uzZ47YsvAnai/I5mwO4YcOG6cknn9SUKVM0b968rMevuOIKnX/++XrzzTeLecYAUPII9oEyokGDBjZ72rt3b73yyis6duyYfvjhB5U2psTjjz/+KOlplAmvvfaa9u/fr7LAZNSHDh2qChUqZI19/fXXqlevnkaPHm3POD3zzDMKDg7WN998U+yvn5GRoccff1xeXl6F2i42NtYG+maO5gzNZZddpgcffFCvv/56juc99NBD+uSTT5ScnFzMMweAkkWwD5RBjRo1UkBAgA4ePFjSU8E5wJTBLF68WH369MkxHhUVpejoaFvLb/j6+urdd99V3759C7TfRx99NEeJWvabCcyz+/jjj7V161bdddddhZr7ypUrlZSUlGNOl156qT1TkX2hcdWqVdW4cWP99ttvhdo/AJR2BPtAGRQTE2Pri7NnWX/66SddddVVatmypQ1sfv75Z5ft3njjDXXo0EGdOnWyZwdMttQwpSQmwDq1pMSMZS/Jeeutt2y9tCnXMHXa2R/LDNJMVt9krDPvZ+4zc+1BRESELado06aNevXqlWOeJqg023br1k1t27bVbbfdpr1797qUCZnacHOW48orr8xRI26sWbNG1113nd3e1Geb91ncNm/ebLPc5nPo2bOnzXpnd+TIEd133302220+73vuuUeRkZE5PgdzM2666Sb7dWYdeub3Yvr06brwwgvtezTf2+7du+uiiy7SunXr7PPi4+P11FNPqXPnzmrXrp3LZ2X2Z0puTNBs5nnNNddo1apVRXq/hw8flo+Pj2rWrJlj3MzJHHDecccd9jMxWrRoofDw8ALt12TbzdmB3G7mLEEmc/bjhRdesCVslSpVKvTcy5cvb4P5TOZ+SEiILQvKzpTymNIkAHASgn2gjDFB47hx42zw1bVrVzu2dOlSG0CbANfUTptA2tzPHozv2rVLixYt0v/+9z/deuutmjp1qmbOnFng183c1gSnpra5adOmdlFjZtnDrFmz7M0Ee4MGDcq6X6VKlRz7MYGhKf0wBx41atTQI488YoN8w9RSmyDXBIHm67i4OBtUp6am2sc3bNigxx57zB4kmNIME2ibkox//vnHPm6eZzK/JpAzByZmP9OmTdO3335bqM/4ueeey5Flzl6WZMqnbr75ZrtQ1byG+do836ynyF4SYrq7mHHzmZnWjiZYNcznk/nZGOZ7ab42n0d2JpNu1mmYgNSUr5hANywsLKsTzn/+8x/7HLO9KUkxB39PPPFEjn188MEHNtA3+zbfB3NAYILfwjLbVK5c2WXclMSY75/Jnvfr189+3gcOHCjwfs33v1mzZrnesh8wmDp787NuStgKKzEx0f48nCowMDDrACyT+YyK8vkAQGnmXdITAFAwAwcOzPraZPRNEFmtWjV73wR7Jis5fvx4e99khHfu3Gmz5Ca7bKSlpdluKiaIMhlvk8F87733dP311xfo9U3G2RxgmEDeBEomaDVBt9mv0apVK/uvCYJN0JR5/1SmO4sJEDOfe+2119psv8m2muD5/vvvt1lowwS3Joj8888/7dkIE0iasxHmcXPAYA5qTFbbz8/PPt8cHJjSElOm0b59e3urW7euKlasWKgfM3OA0b9//6z7JtjO9NFHH9kFqi+++KItWzGfrzkYMItXzXsxTOcj8/mYAyLDrK1YtmyZ/drUtGf/bMz7yO2zMp+D+Z6az3Lw4MF2Eez8+fOzOsaYjL45u2KeY/z11192IWp2Zm7mYMgwZ0LM9/2LL76w3ZwKw7zfzLNApzIHjqa8xxwAfvbZZzbw//DDD+37yo85yMs80DuVh4eHrc83B1HmrMHcuXNVFOZ7lFudv9m/ORDIzvwsZy4+BgCnINgHygiTGTaLdE0LSBPgmlKWTCbQM1nb7EzAb7LamUwZQ/ZsqQn+FixYcNpg69Rxk8U1wbgJZE2gac4imDmYtQOFYboIZcosQzI136bDkKmtnjhxor1lZ7q7mGDfvCcTvN944422jaIJ9k0nlcz9lCtXzgaepnzFlL6YrLYJ/OvXr1+oOZoA22SXM5mDkkzmIMlkhE8N0LNnvs1nZYJq0zlm7dq19gCkevXqhZ5DZlCa/etM5n2ZLL8pITKlS+aA6dTvWeaBQOZBhgnATy1dKQjz3vLKeJufLVNSZA6QzJkOc0bCnDnKjzlLc7qe/ab8yfzMm32ZsxjZS9YKwxzo5XYRMNPJyhy0Zmc+w1PPRAFAWUewD5QRJsht3ry5LYMxway5uJEZM06Xdc0+fmrG0mQ7zeOnC/YzS2Oyl1yYg4NffvnFZm9NZtuU2piMtgkkC8r0Nc+LCezM+8wus97avI55vV9//dXOwZT8vPzyy7aOP7Oe3Jy9WLFihb39+OOPNuA2GW8THBcXc6CUeRYlU2b22HR/MUGvmav595ZbbrEHMsXZ1tFkoM0BjznoMGcTzM18H7P3v8/t58Lcz37AUFAmADbbmVKw7Bl7c2bBlHVlngUxn4sZ+/777wu0X1P2Yw4OcmMCcbNY1qxPMT/rpzLlVeag0LxeXszzzNkQUwJmzrYY5hoVZuzUwN6coTHrKwDASThfCZQxJrAzixSzB48my3xqb3tTNpI9+2wWUmavpzZnA0wA7+3tbW9G9rIGUzKSnSmnMK9h6qZNDbUp1TClPWa9wKllE5k19rk5XetEc9bBbGtKcTLrts2ZDJO5zlw0uXDhQlt/bwJ3UwpkFnKa52cGl6bcwxyEmPIdcwbEtFI02X2TZS/OTkjmQMjMLXOeJgjOXP9gPnfzuZgSKhPom1KaUxcZZzLvN7MMqjDMGgBzxsAsYjVrFEx5Tm618uaAJ5MJmk1pV+YBYmGYQN+crTAHWtmZA5tTf+7Mmgbzc1UcNftmkfGpC3dN4G/ONJivC3Jxrdq1a9sg35yVymTK18xZoMzgP/PskTnrYc4oAICTkNkHyhgTIN5+++128eeoUaNsRtsslDW10+ZKoKa0xmTgMzPfp9aBm2DJtDA0NdCZ9dwmw2nq401A//DDD9vs/amLd02HGRPAmgW5Zq1AZhecWrVq5XieCa7NY6bUxzzXlEsU5Oq9/v7+9n2Z1zDv0QTTZg6m3j0zs2sORkw21wSfJmg1tfymBChzDqbcxizcNQcv5vVN+YbJ4l599dUqLiajbg50TLcd8/XRo0c1YcIE2wnJMJ+jYcpTTOmROUAxdf65LXA1n9Wnn35q37s5GDNnMLJ3jTkdE6iaz8BcGMq8V3MVW7Mw2zAHWpkHb6a8x5TBmIMBE+CaszuZ6yEKyxy4mHUjZs1
2025-09-16 09:59:38 +08:00
},
"metadata": {},
2025-11-07 16:26:00 +08:00
"output_type": "display_data",
"jetTransient": {
"display_id": null
}
2025-09-16 09:59:38 +08:00
}
],
2025-12-16 00:36:36 +08:00
"execution_count": 49
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
}