From cc2bd519e73b131ad38ca568d0ac2c1e9a7138e7 Mon Sep 17 00:00:00 2001 From: fish Date: Fri, 24 Jul 2026 12:24:34 +0800 Subject: [PATCH] =?UTF-8?q?Python=E6=89=A7=E8=A1=8C=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E9=99=90=E5=88=B6=E4=B8=BADocker=EF=BC=9B=E5=BB=BA=E6=9D=90?= =?UTF-8?q?=E6=8C=87=E6=95=B0=E7=9C=8B=E6=9D=BF=E6=96=B0=E5=A2=9E=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=85=A8=E6=99=AF=E5=9B=BE=E8=A1=A8=E5=92=8C=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 13 +- CLAUDE.md | 13 +- 建材指数监控/.gitignore | 1 + 建材指数监控/index.html | 196 ------------- 建材指数监控/index_template.html | 462 +++++++++++++++++++++++++++++++ 建材指数监控/refresh.py | 114 +++++++- 6 files changed, 584 insertions(+), 215 deletions(-) create mode 100644 建材指数监控/.gitignore delete mode 100644 建材指数监控/index.html create mode 100644 建材指数监控/index_template.html diff --git a/AGENTS.md b/AGENTS.md index 04734b9..f8f2cc7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,16 +8,11 @@ AI 协助分析交易数据和回测的仓库。主要用来做策略回测、 ## Python 执行环境 -按优先级选择: +**仅允许使用 Docker 运行 Python**,严禁使用设备本地 Python: -1. **Docker(推荐)** — 隔离环境,避免依赖冲突 - - **⚠️ 严禁直接 `docker run python:3.11-slim`**,必须先执行 `docker images` 确认本地已有镜像,优先复用 - - 无可用镜像时才允许拉取,默认拉取 `python:3.11-slim` - - 示例: `docker images | grep python` 查看已有镜像,再用已有镜像执行 -2. **设备 Python + venv** — Docker 不可用时的降级方案 - - 检测 Python: `which python3 || which python`,取其路径 - - 在项目目录创建 `.venv/`(已 gitignore),` -m venv .venv && source .venv/bin/activate` - - 安装包: `pip install ` +- **⚠️ 严禁直接 `docker run python:3.11-slim`**,必须先执行 `docker images` 确认本地已有镜像,优先复用 +- 无可用镜像时才允许拉取,默认拉取 `python:3.11-slim` +- 示例: `docker images | grep python` 查看已有镜像,再用已有镜像执行 ## Data Sources diff --git a/CLAUDE.md b/CLAUDE.md index 99b2089..7e425c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,16 +8,11 @@ AI 协助分析交易数据和回测的仓库。主要用来做策略回测、 ## Python 执行环境 -按优先级选择: +**仅允许使用 Docker 运行 Python**,严禁使用设备本地 Python: -1. **Docker(推荐)** — 隔离环境,避免依赖冲突 - - **⚠️ 严禁直接 `docker run python:3.11-slim`**,必须先执行 `docker images` 确认本地已有镜像,优先复用 - - 无可用镜像时才允许拉取,默认拉取 `python:3.11-slim` - - 示例: `docker images | grep python` 查看已有镜像,再用已有镜像执行 -2. **设备 Python + venv** — Docker 不可用时的降级方案 - - 检测 Python: `which python3 || which python`,取其路径 - - 在项目目录创建 `.venv/`(已 gitignore),` -m venv .venv && source .venv/bin/activate` - - 安装包: `pip install ` +- **⚠️ 严禁直接 `docker run python:3.11-slim`**,必须先执行 `docker images` 确认本地已有镜像,优先复用 +- 无可用镜像时才允许拉取,默认拉取 `python:3.11-slim` +- 示例: `docker images | grep python` 查看已有镜像,再用已有镜像执行 ## Data Sources diff --git a/建材指数监控/.gitignore b/建材指数监控/.gitignore new file mode 100644 index 0000000..dcaf716 --- /dev/null +++ b/建材指数监控/.gitignore @@ -0,0 +1 @@ +index.html diff --git a/建材指数监控/index.html b/建材指数监控/index.html deleted file mode 100644 index e4abf56..0000000 --- a/建材指数监控/index.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - -建材价格指数 · 趋势监控 - - - - - - -

建材价格指数 · 趋势监控

-

- - -
-
-
最新值
-
-
-
-
-
近3月涨跌
-
-
-
-
近6月涨跌
-
-
-
-
近1年涨跌
-
-
-
- - -
- - -

均线系统

- - - -
均线数值方向价格位置
- - -

近30日明细

- - - -
日期指数日涨跌%MA5MA20MA60
- -

数据来源:akshare macro_china_construction_index · 建材综合指数

- - - - diff --git a/建材指数监控/index_template.html b/建材指数监控/index_template.html new file mode 100644 index 0000000..1dd1961 --- /dev/null +++ b/建材指数监控/index_template.html @@ -0,0 +1,462 @@ + + + + + +建材价格指数 · 趋势监控 + + + + + + +

建材价格指数 · 趋势监控

+

+ + +
+
+
最新值
+
+
+
+
+
近3月涨跌
+
+
+
+
近6月涨跌
+
+
+
+
近1年涨跌
+
+
+
+ + +
+ + +

历史全景 2011.12 – 2026.07

+
+ + + +

周期对比

+ + + +
周期阶段区间谷 → 峰涨幅
+

+ + +

均线系统

+ + + +
均线数值方向价格位置
+ + +

近30日明细

+ + + +
日期指数日涨跌%MA5MA20MA60
+ +

数据来源:akshare macro_china_construction_index · 建材综合指数

+ + + + diff --git a/建材指数监控/refresh.py b/建材指数监控/refresh.py index 17ad9bd..05634a1 100644 --- a/建材指数监控/refresh.py +++ b/建材指数监控/refresh.py @@ -66,19 +66,131 @@ for _, r in df.tail(30).iterrows(): 'MA60': None if pd.isna(r['MA60']) else round(float(r['MA60']), 1), }) + +def build_history(df): + """构建历史全景数据:月度采样 + 周期标注 + 统计。""" + + # 月度采样(取每月最后一条有效记录) + monthly = df.set_index('日期').resample('ME')['最新值'].last().dropna().reset_index() + ma60_monthly = df.set_index('日期').resample('ME')['MA60'].last().dropna().reset_index() + monthly_vals = [] + for _, r in monthly.iterrows(): + ma_row = ma60_monthly[ma60_monthly['日期'] == r['日期']] + ma60_val = None if ma_row.empty or pd.isna(ma_row['MA60'].iloc[0]) else round(float(ma_row['MA60'].iloc[0]), 1) + monthly_vals.append({ + 'date': r['日期'].strftime('%Y-%m'), + 'value': int(r['最新值']), + 'ma60': ma60_val, + }) + + # 历史统计 + vals = df['最新值'] + latest_val = float(vals.iloc[-1]) + pct_rank = round((vals < latest_val).sum() / len(vals) * 100, 1) + + # 年度涨跌(排除不足 200 个交易日的首尾年) + df['year'] = df['日期'].dt.year + yearly = df.groupby('year')['最新值'].agg(['first', 'last', 'count']) + yearly = yearly[yearly['count'] >= 200] + yearly['chg'] = ((yearly['last'] - yearly['first']) / yearly['first'] * 100).round(1) + down_years = int((yearly['chg'] < 0).sum()) + ytd = float(yearly['chg'].iloc[-1]) if len(yearly) > 0 else 0 + + # 识别主要周期:用未取整的 MA60 找极值,间距至少 4 个月 + ma60_raw = df['最新值'].rolling(60).mean().values # 未取整,保留精度 + all_turns = [] # (date, value, type) + min_gap = 80 # 交易日,约 4 个月 + last_turn_idx = -999 + for i in range(2, len(ma60_raw) - 2): + if pd.isna(ma60_raw[i]): + continue + if ma60_raw[i] > ma60_raw[i - 1] and ma60_raw[i] > ma60_raw[i - 2] and ma60_raw[i] > ma60_raw[i + 1] and ma60_raw[i] > ma60_raw[i + 2]: + if i - last_turn_idx >= min_gap: + all_turns.append((df['日期'].iloc[i], float(ma60_raw[i]), 'peak')) + last_turn_idx = i + elif ma60_raw[i] < ma60_raw[i - 1] and ma60_raw[i] < ma60_raw[i - 2] and ma60_raw[i] < ma60_raw[i + 1] and ma60_raw[i] < ma60_raw[i + 2]: + if i - last_turn_idx >= min_gap: + all_turns.append((df['日期'].iloc[i], float(ma60_raw[i]), 'trough')) + last_turn_idx = i + + # 构建周期(谷 → 下一个峰) + cycles = [] + for i, (t_date, t_val, t_type) in enumerate(all_turns): + if t_type != 'trough': + continue + # 找下一个峰 + for j in range(i + 1, len(all_turns)): + if all_turns[j][2] == 'peak': + p_date, p_val, _ = all_turns[j] + gain = round((p_val - t_val) / t_val * 100, 1) + if gain > 10: + label = '' + if t_date.year == 2015 or t_date.year == 2016: + label = '供给侧改革' + elif t_date.year == 2020: + label = '疫情后刺激' + cycles.append({ + 'label': label, + 'start': t_date.strftime('%Y-%m'), + 'end': p_date.strftime('%Y-%m'), + 'from': int(t_val), + 'to': int(p_val), + 'pct': gain, + }) + break + + # 当前反弹(用价格实际谷底,不是 MA60 谷底) + # 找最近 2 年的价格最低点 + recent_2y = df[df['日期'] >= df['日期'].iloc[-1] - pd.DateOffset(years=2)] + trough_idx = recent_2y['最新值'].idxmin() + trough_date = df.loc[trough_idx, '日期'] + trough_val = float(df.loc[trough_idx, '最新值']) + cur_gain = round((latest_val - trough_val) / trough_val * 100, 1) + current_rebound = { + 'label': '当前反弹', + 'start': trough_date.strftime('%Y-%m'), + 'end': None, + 'from': int(trough_val), + 'to': int(latest_val), + 'pct': cur_gain, + } + + # 均线结构 + last = df.iloc[-1] + ma20_vs_ma60 = 'above' if last['MA20'] > last['MA60'] else 'below' + + return { + 'monthly': monthly_vals, + 'stats': { + 'mean': round(float(vals.mean()), 1), + 'min': int(vals.min()), + 'max': int(vals.max()), + 'min_date': df.loc[vals.idxmin(), '日期'].strftime('%Y-%m'), + 'max_date': df.loc[vals.idxmax(), '日期'].strftime('%Y-%m'), + 'percentile': pct_rank, + 'down_years': down_years, + 'ytd_chg': round(ytd, 1), + }, + 'cycles': cycles, + 'current_rebound': current_rebound, + 'ma20_vs_ma60': ma20_vs_ma60, + } + + output = { 'summary': summary, 'mas': mas, 'signals': [{'period': p, 'signal': s, 'detail': d} for p, s, d in signals], 'overall': {'signal': overall[0], 'color': overall[1]}, 'recent': rows, + 'history': build_history(df), } with open('data.json', 'w') as f: json.dump(output, f, ensure_ascii=False, indent=2) SENTINEL = '/*__DATA_SENTINEL__*/{}' -html = open('index.html').read() +html = open('index_template.html').read() html = html.replace(SENTINEL, json.dumps(output, ensure_ascii=False)) open('index.html', 'w').write(html)