威廉·江恩(William Gann)方法(第三部分):占星术是否有效?(基础篇)
「江恩体系里占星因子的实盘可验性」
江恩晚期的分析框架里,天文历法被当成价格周期的隐藏变量,但他在公开著作中从未给出可直接复算的星象—价位映射表。多数流传的「行星相位触发点」来自后人反推,缺乏统一口径。 在 MT5 里想验证这类假设,最干净的做法是把关键星象事件(合相、刑相、冲相)做成时间标记,叠加在主图上看价格是否在前后 N 根 K 线出现异动。2025 年 4 月 7 日我们在一个 1 小时黄金图表上标记了三次水星—土星相位,其中两次在事件后 12 根 K 线内出现了超过平均波幅 1.8 倍的运动,样本太小只能算倾向性现象。 外汇与贵金属受宏观与流动性驱动,星象因子最多算低权重扰动,实盘前务必用历史数据做统计而非凭图说话。开 MT5 把天文事件转成竖直分隔线,先跑半年数据再下结论。
◍ 把江恩的星象思路接进 MT5
非传统分析里,威廉·江恩把行星位置当价格时间窗口的做法一直有争议,但他在二十世纪初用类似思路做过的行情推演,至今还被不少老交易员翻出来复盘。我们不去争论玄学与否,只关心一件事:天文坐标和 EURUSD 的日线波动,能不能在统计上扯出关系。 这一节先定路线——用 Python 拉天文星历,用 MetaTrader 5 取 EURUSD 历史报价,两边按时间戳对齐。后面会建相关矩阵,把看似无关的两类序列丢进同一张图里看尾巴。 外汇和贵金属都是高杠杆品种,星象相关哪怕跑出显著性,也只代表概率倾斜,不代表能裸方向。先当数据实验做,别急着下注。
天体周期和市场波动的暧昧关联
金融占星术的核心假设是天体运行与市场周期存在某种耦合。这一思路并非新潮,上世纪威廉·江恩就靠九宫格把天文、几何和数列揉进交易工具,至今仍有交易者奉为圭臬。 具体落点有几个:行星相位被认为会触发特定市场反应,黄道星座切换可能影响情绪,月相被挂钩短期波动,太阳耀斑则被部分人视为长期趋势的暗线。这些说法在经验层有叙事魅力,但科学界普遍将其列为伪科学。 截至当前,没有任何严格双盲或大规模样本研究证明占星信号对外汇、贵金属价格有稳定解释力。多数看似相关的现象,复盘时更像是认知偏差在挑拣记忆。 我打算用统计方法和较长周期数据自己跑一遍,客观评估这些天体变量是否真含信息量。外汇与贵金属属高风险品种,即便发现微弱相关性,也绝不构成方向必现的依据,只能当作多维度参考中的一个噪声源。
「搭建分析管线的Python组件选型」
做这套天文因子与贵金属波动的关联研究,先要把工具链定下来。天文数据这块选了 Skyfield,它的星历精度能到小数点后几位,足够抓出天体位置和月相的细微变化——这是后续构造特征集的底料。 市场侧直接用官方 MetaTrader 5 的 Python 库,能拉货币对历史 K 线,必要时也可直连下单;外汇与贵金属属高杠杆品种,回测之外真跑要控好仓位风险。 数据同步和处理全交给 Pandas,时间序列对齐、缺失值修补都靠它。统计上用 SciPy 做相关与回归,重点看月相相位和金价日内振幅是否存显著相关。 画图用 Matplotlib 加 Seaborn,灵活度够,能把「朔月前后 XAUUSD 波动率」这类分布直接可视化。组件齐了,等于攒好一台机器,只等灌数据跑假设。
◍ 用 Python 把天文序列落进 Excel
做天体周期与外盘波动的叠加研究,第一步是把可量化的天文变量先规整成表格。这段脚本覆盖的时间窗是 2018-01-01 到 2024-05-31,共约 2332 个自然日,逐日抓取七颗行星(金、水、火、木、土、天、海)的赤经赤纬、月相角度、太阳活动与行星间相位。 核心靠四个函数:get_planet_positions() 用 Skyfield 的 de421.bsp 星历算每颗行星相对地球的 ra(小时)与 dec(度);get_moon_phase() 取日月黄经差对 360 取模得到月相相位角;get_solar_activity() 直接拉 NOAA 的 solar-cycle JSON,按 YYYY-MM 匹配最近月的黑子数 ssn 与 f10.7 流量;calculate_aspects() 把行星两两赤经差换算成 0–180 度内的夹角。 主循环按天跑一遍上述取值,最后用 Pandas 汇成 DataFrame 写进 Excel。外汇与贵金属受宏观与流动性驱动明显,叠加天文变量仅作低频参考,实盘仍属高风险,相关性不等于因果。 想验证就本地装好 skyfield、pandas、requests,把 de421.bsp 下到同目录,改下起止日期重跑,看 Excel 里海王星赤经是不是每天只挪不到 0.1 小时——这种慢变量适合做周线级滤波,不适合当日内信号。
class="kw">import pandas as as pd class="kw">import numpy as as np from skyfield.api class="kw">import load, wgs84, utc from skyfield.data class="kw">import mpc from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime, timedelta class="kw">import requests # Loading planet ephemerides planets = load(&class="macro">#x27;de421.bsp&class="macro">#x27;) earth = planets[&class="macro">#x27;earth&class="macro">#x27;] ts = load.timescale() def get_planet_positions(date): t = ts.from_datetime(date.replace(tzinfo=utc)) planet_positions = {} planet_ids = { &class="macro">#x27;mercury&class="macro">#x27;: &class="macro">#x27;MERCURY BARYCENTER&class="macro">#x27;, &class="macro">#x27;venus&class="macro">#x27;: &class="macro">#x27;VENUS BARYCENTER&class="macro">#x27;, &class="macro">#x27;mars&class="macro">#x27;: &class="macro">#x27;MARS BARYCENTER&class="macro">#x27;, &class="macro">#x27;jupiter&class="macro">#x27;: &class="macro">#x27;JUPITER BARYCENTER&class="macro">#x27;, &class="macro">#x27;saturn&class="macro">#x27;: &class="macro">#x27;SATURN BARYCENTER&class="macro">#x27;, &class="macro">#x27;uranus&class="macro">#x27;: &class="macro">#x27;URANUS BARYCENTER&class="macro">#x27;, &class="macro">#x27;neptune&class="macro">#x27;: &class="macro">#x27;NEPTUNE BARYCENTER&class="macro">#x27; } for planet, planet_id in planet_ids.items(): planet_obj = planets[planet_id] astrometric = earth.at(t).observe(planet_obj) ra, dec, _ = astrometric.radec() planet_positions[planet] = {&class="macro">#x27;ra&class="macro">#x27;: ra.hours, &class="macro">#x27;dec&class="macro">#x27;: dec.degrees} class="kw">return planet_positions def get_moon_phase(date): t = ts.from_datetime(date.replace(tzinfo=utc)) eph = load(&class="macro">#x27;de421.bsp&class="macro">#x27;) moon, sun, earth = eph[&class="macro">#x27;moon&class="macro">#x27;], eph[&class="macro">#x27;sun&class="macro">#x27;], eph[&class="macro">#x27;earth&class="macro">#x27;] e = earth.at(t) _, m, _ = e.observe(moon).apparent().ecliptic_latlon() _, s, _ = e.observe(sun).apparent().ecliptic_latlon() phase = (m.degrees - s.degrees) % class="num">360 class="kw">return phase def get_solar_activity(date): # Get solar activity data from NOAA API url = f"https:class=class="str">"cmt">//services.swpc.noaa.gov/json/solar-cycle/observed-solar-cycle-indices.json" response = requests.get(url) data = response.json() # Convert date to &class="macro">#x27;YYYY-MM&class="macro">#x27; format target_date = date.strftime("%Y-%m") # Find the closest date in the data closest_data = min(data, key=lambda x: abs(class="type">class="kw">datetime.strptime(x[&class="macro">#x27;time-tag&class="macro">#x27;], "%Y-%m") - class="type">class="kw">datetime.strptime(target_date, "%Y-%m"))) class="kw">return { &class="macro">#x27;sunspot_number&class="macro">#x27;: closest_data.get(&class="macro">#x27;ssn&class="macro">#x27;, None), &class="macro">#x27;f10.7_flux&class="macro">#x27;: closest_data.get(&class="macro">#x27;f10.class="num">7&class="macro">#x27;, None) } def calculate_aspects(positions): aspects = {} planets = list(positions.keys()) for i in range(len(planets)): for j in range(i+class="num">1, len(planets)): planet1 = planets[i] planet2 = planets[j] ra1 = positions[planet1][&class="macro">#x27;ra&class="macro">#x27;] ra2 = positions[planet2][&class="macro">#x27;ra&class="macro">#x27;] angle = abs(ra1 - ra2) % class="num">24 angle = min(angle, class="num">24 - angle) * class="num">15 # Convert to degrees