Files
us-stock-index-drawdown-ana…/package_results.py

70 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import shutil
import zipfile
from pathlib import Path
ROOT=Path(__file__).resolve().parent
OUT=ROOT/'outputs'/'20261019'
a=json.loads((ROOT/'analysis.json').read_text())
lines=['# 纳斯达克综合与标普500:日线回撤及解套时间','',
'抓取日期:2026-10-19(北京时间)。行情截至美国交易日 **2026-10-16**。NASDAQ 在本报告指纳斯达克综合指数 ^IXIC,不是纳斯达克100。','',
'## 数据范围','',
'| 指数 | 起点 | 终点 | 有效日线 | 回撤段数 | 已解套 | 未解套 |',
'|---|---|---|---:|---:|---:|---:|']
for x in a['indices'].values():
lines.append(f"| {x['name']} | {x['start']} | {x['end']} | {x['observations']:,} | {len(x['events'])} | {len(x['events'])-len(x['open_events'])} | {len(x['open_events'])} |")
lines += ['', '标普500于1957-03-04正式推出;更早数据是数据商提供的前身/回溯序列,不代表当时已有现行500只股票指数。早期日线没有逐日与独立交易所日历核对,可能未含当年的周六交易。因此“交易日”严格指本数据集的有效日线间隔数;自然日数直接由日期相减。','',
'## 计算口径','',
'- 价格:名义指数日收盘价,不含分红再投资、税费、交易成本、汇率或通胀。价格比较统一到0.01点,消除Yahoo浮点表示噪声。',
'- 回撤:日收盘低于截至此前的历史最高收盘后开始,首次收盘大于或等于该前高时结束。所有严格低于前高的事件均保留,没有跌幅或时长门槛。',
'- 前高日:下跌前最后一个达到该历史最高收盘的日期。谷底日:该事件最低收盘首次出现的日期。',
'- 总解套自然日 = 解套日 − 前高日。下跌自然日 = 谷底日 − 前高日。谷底后恢复自然日 = 解套日 − 谷底日。',
'- 交易日:对应日期在有效日线中的序号相减,不含起点、包含终点。不填充休市日,不插值。',
'- 未解套:解套日期、最终总时长和恢复时长留空;“已观察天数”只累计至2026-10-16。它是右删失的未完成事件,不参加已解套时长均值/中位数。',
'- 一段长回撤包含期间所有反弹,不重复拆分。例如纳指2000—2015年算一段,内部2007年的局部高点不另起历史新高回撤。',
'- 为补充任意日期买入的情况,日线表另列每个买入日之后,首个收盘不低于该日买入价的日期。若次日即盈利,也会列出该日期,但“期间曾浮亏”为否。这列不是历史新高回撤时长。最后一个观测日标为“最后观测日”,不能直接视为亏损。',
'- 事件边界和首次回本日期由脚本识别,是抓取时点快照。Excel可重算简单比率与日期差,但修改价格或新增行情后,须重跑分析脚本以重新识别边界。','',
'## 最大跌幅至少10%的全部回撤','',
'下面仍按“历史新高到收复该前高”的不重叠口径。完整微小回撤也已保存在Excel和CSV。','',
'| 指数 | 前高日 | 谷底日 | 解套日 | 最大回撤 | 下跌自然日 | 谷底后恢复自然日 | 总解套自然日 | 总解套交易日 |',
'|---|---|---|---|---:|---:|---:|---:|---:|']
for x in a['indices'].values():
for e in x['events']:
if e['max_drawdown'] <= -.10:
f=lambda v:'未解套' if v is None else str(v)
lines.append(f"| {x['name']} | {e['peak_date']} | {e['trough_date']} | {f(e['recovery_date'])} | {e['max_drawdown']:.2%} | {e['decline_calendar_days']} | {f(e['rebound_calendar_days'])} | {f(e['total_calendar_days'])} | {f(e['total_trading_days'])} |")
lines += ['', '## 目前尚未解套','', '| 指数 | 前高日 | 截止日 | 期间最大回撤 | 已观察自然日 | 已观察交易日 |','|---|---|---|---:|---:|---:|']
for x in a['indices'].values():
for e in x['open_events']:
lines.append(f"| {x['name']} | {e['peak_date']} | {e['end_date']} | {e['max_drawdown']:.2%} | {e['observed_calendar_days']} | {e['observed_trading_days']} |")
lines += ['', '## 数据来源与交叉核验','',
'两项指数均以Yahoo同指数日线的有效观测日期为日期基准。在这些日期上,FRED有有效收盘价时优先采用FRED(其上游为指数公司),FRED没有时使用Yahoo。不是总回报指数,不拼接ETF,不把纳指100混入综合指数。','']
for x in a['indices'].values():
lines.append(f"- {x['name']}:{x['fred_rows']:,}条FRED,{x['yahoo_rows']:,}条Yahoo。两源重叠{x['source_overlap']:,}条,按0.01点比较有{x['source_mismatches']:,}条不同,全部保留在source_discrepancies.csv,最终选FRED。")
lines += ['','FRED的纳指序列另含2019-04-19一条有效数值,但该日为Good Friday休市且Yahoo无观测。本报告排除此异常行,避免多计交易日。原始响应原样保留。', '',
'FRED的SP500依协议仅提供最近10年数据,因此更早标普日线依赖Yahoo,未获得独立全历史权威源逐日交叉核验。纳指末日2026-10-16仅Yahoo有观测。数据未来可能修订。','',
'- [FRED NASDAQCOM:来源、日收盘定义](https://fred.stlouisfed.org/series/NASDAQCOM)',
'- [FRED SP500:来源、仅10年限制、价格指数不含分红](https://fred.stlouisfed.org/series/SP500)',
'- [Yahoo纳指历史页](https://finance.yahoo.com/quote/%5EIXIC/history/)',
'- [Yahoo标普历史页](https://finance.yahoo.com/quote/%5EGSPC/history/)',
'- [标普500官方说明、推出日期](https://www.spglobal.com/spdji/en/indices/equity/sp-500/)',
'- [NYSE官方2019年休市日历](https://ir.theice.com/press/news-details/2018/NYSE-Group-Announces-2019-2020-and-2021-Holiday-and-Early-Closings-Calendar/default.aspx)',
'', '## 检查与文件','',
'检查了日期递增、重复日期、正数收盘、事件数与独立累计最高值算法一致、每段最低点、收复日首次达标、不重叠、日期差可加和。逐日回本使用O(n)单调栈,并对抽样日期及所有事件前高/谷底用独立逐日查找核对。另检查同价前高、重复谷底、未解套与没有回撤等人工边界样本。检查结果见validation.json。','',
'- nasdaq_sp500_drawdown_recovery.xlsx:概览、全部回撤、日线与逐日回本三个工作表。',
'- nasdaq_drawdowns.csv / sp500_drawdowns.csv:每次回撤的完整明细。',
'- nasdaq_daily.csv / sp500_daily.csv:逐条来源、每日回撤及买入回本日期。',
'- source_discrepancies.csv:两来源不一致的观测。',
'- source_manifest.json:下载URL、时间戳、字节数和SHA-256校验。raw/为未经改写的响应。',
'- analyze.py:可用Python标准库复算。先运行fetch_data.py更新固定截止日期的行情,再运行analyze.py;Excel用build_workbook.mjs重建。','']
(OUT/'分析说明.md').write_text('\n'.join(lines))
shutil.copy2(ROOT/'source_manifest.json',OUT/'source_manifest.json')
archive=OUT/'nasdaq_sp500_data_and_analysis.zip'
with zipfile.ZipFile(archive,'w',zipfile.ZIP_DEFLATED) as z:
for p in OUT.iterdir():
if p.suffix in ('.csv','.json','.md','.xlsx'):
z.write(p,p.name)
for p in (ROOT/'raw').iterdir():z.write(p,'raw/'+p.name)
for name in ['fetch_data.py','analyze.py','build_workbook.mjs','package_results.py']:z.write(ROOT/name,'scripts/'+name)
print(archive)