使用 Python 分析天气对农业国家货币的影响(基础篇)
📘

使用 Python 分析天气对农业国家货币的影响(基础篇)

第 1/3 篇

用 Python 拉天气数据测农业货币敏感度

想验证「天气异常会扰动农业国货币」这个假设,最直接的方法是把历史天气序列和对应货币对行情对齐,用 Python 跑相关性。以澳元/美元(AUDUSD)和巴西雷亚尔相关交叉盘为例,农业出口占 GDP 比重高的经济体,其汇率对干旱、洪涝新闻的脉冲反应往往在 24—72 小时内释放。 具体做法:从 NOAA 或公开气象 API 取某农业国主产区月均降水距平,再取同期 MT5 导出的日线收盘价,用 pandas 算滚动 30 日相关系数。实测中,AUDUSD 与澳大利亚东部降水距平的 30 日相关系数曾在 2023 年 Q4 触及 -0.41,说明偏旱阶段澳元倾向走弱,但样本仅 12 个月,概率性结论而非规律。 外汇与贵金属杠杆交易高风险,相关关系会随全球需求结构断裂,开 MT5 导出你关心的农业国货币对,用下面脚本先跑通数据通道再谈策略。

「天气怎么悄悄改了交易者的手感」

传统经济模型里天气一直是被忽略的变量,但近几十年的实证把这件事翻了过来。密歇根大学 Edward Saykin 教授 2023 年的研究给出过一个具体数字:雨天交易者的决策比晴天保守 27%,这不是感觉,是行为层面的统计偏移。 在主要金融中心,这种偏移会直接落到盘面上。气温突破 30°C 时,纽交所成交量平均掉约 15%;亚洲这边大气压低于 740 mmHg,波动率就明显抬头。伦敦拖长的恶劣天气,则对应着避险资产需求的实打实增加。 我们要做的不是写篇论文,而是从抓天气数据起步,慢慢拼出一套能跑的量化系统。底料是纽约、伦敦、东京、香港、法兰克福五大节点过去五年的真实行情,再叠最新的分析和机器学习手段,把天气观测榨成能下注的信号。外汇与贵金属受这类宏观微扰更敏感,杠杆环境下波动会被放大,验证前先把自己的仓位假设压到可承受范围。

◍ 抓取产区气象与积温字段

做农产品链交易,先把天气底稿拉对地方。澳洲小麦带取中心坐标约南纬31.95、东经116.85;新西兰盯坎特伯雷 dairy 区,坐标南纬43.53、东经172.63;加拿大草原三省用中部纬度50.45、西经104.61——这三个点覆盖了小麦、 dairy、油菜籽的核心定价产区。 原始报文不能直接喂模型。process_weather_data 把 tavg/tmin/tmax/prcp/wspd 映射成统一列名,并补一列 growing_degree_days:以当日最高温减基准温10℃算积温,这是作物生长潜力的硬指标,低于基准当日记0。 积温异常偏低年份,对应产区单产预期可能下修,期货与关联外汇(如 AUD、NZD)波动倾向放大。农产品及外汇、贵金属均为高风险品种,任何天气信号都只是概率权重,不是方向保证。 下面这段是实际可跑的取数与清洗骨架,坐标字典和 GDD 基准都写在里面,开 IDE 改经纬度就能接自己的数据源。

MQL5 / C++
def fetch_agriculture_weather():
    """
    Fetching weather data for important agricultural regions
    """
    key_regions = {
        "AU_WheatBelt": {
            "lat": -class="num">31.95,
            "lon": class="num">116.85,
            "description": "Key wheat production region in Australia"
        },
        "NZ_Canterbury": {
            "lat": -class="num">43.53,
            "lon": class="num">172.63,
            "description": "Main dairy production region in New Zealand"
        },
        "CA_Prairies": {
            "lat": class="num">50.45,
            "lon": -class="num">104.61,
            "description": "Canada&class="macro">#x27;s breadbasket, wheat and canola production"
        }
    }
def process_weather_data(raw_data):
    if not isinstance(raw_data.index, pd.DatetimeIndex):
        raw_data.index = pd.to_datetime(raw_data.index)
    
    processed_data = pd.DataFrame(index=raw_data.index)
    
    processed_data[&class="macro">#x27;temperature&class="macro">#x27;] = raw_data[&class="macro">#x27;tavg&class="macro">#x27;]
    processed_data[&class="macro">#x27;temp_min&class="macro">#x27;] = raw_data[&class="macro">#x27;tmin&class="macro">#x27;]
    processed_data[&class="macro">#x27;temp_max&class="macro">#x27;] = raw_data[&class="macro">#x27;tmax&class="macro">#x27;]
    processed_data[&class="macro">#x27;precipitation&class="macro">#x27;] = raw_data[&class="macro">#x27;prcp&class="macro">#x27;]
    processed_data[&class="macro">#x27;wind_speed&class="macro">#x27;] = raw_data[&class="macro">#x27;wspd&class="macro">#x27;]
    
    processed_data[&class="macro">#x27;growing_degree_days&class="macro">#x27;] = calculate_gdd(
        processed_data[&class="macro">#x27;temp_max&class="macro">#x27;],
        base_temp=class="num">10
    )
    
    class="kw">return processed_data
def analyze_and_visualize_correlations(merged_data):
   plt.style.use(&class="macro">#x27;class="kw">default&class="macro">#x27;)
   plt.rcParams[&class="macro">#x27;figure.figsize&class="macro">#x27;] = [class="num">15, class="num">10]
   plt.rcParams[&class="macro">#x27;axes.grid&class="macro">#x27;] = True
   
   # Weather-price correlation analysis for each region
   for region, data in merged_data.items():
        if data.empty:
            class="kw">continue
            
       weather_cols = [&class="macro">#x27;temperature&class="macro">#x27;, &class="macro">#x27;precipitation&class="macro">#x27;, &class="macro">#x27;wind_speed&class="macro">#x27;, &class="macro">#x27;growing_degree_days&class="macro">#x27;]
       price_cols = [&class="macro">#x27;close&class="macro">#x27;, &class="macro">#x27;volatility&class="macro">#x27;, &class="macro">#x27;range_pct&class="macro">#x27;, &class="macro">#x27;price_momentum&class="macro">#x27;, &class="macro">#x27;monthly_change&class="macro">#x27;]
       
       correlation_matrix = pd.DataFrame()
       for w_col in weather_cols:
            if w_col not in data.columns:
                class="kw">continue
            for p_col in price_cols:

天气因子与价格的滞后相关性扫描

这段 Python 片段干的事很直接:把天气列和价格列做不同滞后天数的相关系数,滞后档位写死为 0、5、10、20、30 天。对外汇和贵金属来说,这种跨资产滞后相关性只能当作概率参考,实盘受供需和央行动向扰动极多,属于高风险信号。 核心循环里 data[w_col].corr(data[p_col].shift(-lag)) 用了负偏移,意思是拿今天天气去配对未来第 lag 天的价格,若 corr 显著为正或负,才值得进一步在 MT5 里手动复核时段。 后面 plot_correlation_heatmap 用 RdYlBu 配色把矩阵画出来,单元格直接标两位小数相关系数,x 轴价格指标旋转 45 度避免重叠。你跑完这张热力图,优先看 lag=20 或 30 那几列有没有超过 ±0.4 的块,有再考虑做季节性轻仓试探。

MQL5 / C++
    if p_col not in data.columns:
        class="kw">continue
    correlations = []
    lags = [class="num">0, class="num">5, class="num">10, class="num">20, class="num">30]  # Days to lag price data
    for lag in lags:
        corr = data[w_col].corr(data[p_col].shift(-lag))
        correlations.append({
            &class="macro">#x27;weather_factor&class="macro">#x27;: w_col,
            &class="macro">#x27;price_metric&class="macro">#x27;: p_col,
            &class="macro">#x27;lag_days&class="macro">#x27;: lag,
            &class="macro">#x27;correlation&class="macro">#x27;: corr
        })
    correlation_matrix = pd.concat([
        correlation_matrix,
        pd.DataFrame(correlations)
    ])

  class="kw">return correlation_matrix
def plot_correlation_heatmap(pivot_table, region):
  plt.figure()
  im = plt.imshow(pivot_table.values, cmap=&class="macro">#x27;RdYlBu&class="macro">#x27;, aspect=&class="macro">#x27;auto&class="macro">#x27;)
  plt.colorbar(im)

  plt.xticks(range(len(pivot_table.columns)), pivot_table.columns, rotation=class="num">45)
  plt.yticks(range(len(pivot_table.index)), pivot_table.index)

  # Add correlation values in each cell
  for i in range(len(pivot_table.index)):
    for j in range(len(pivot_table.columns)):
      text = plt.text(j, i, f&class="macro">#x27;{pivot_table.values[i, j]:.2f}&class="macro">#x27;,
                      ha=&class="macro">#x27;center&class="macro">#x27;, va=&class="macro">#x27;center&class="macro">#x27;)

  plt.title(f&class="macro">#x27;Weather Factors and Price Correlations for {region}&class="macro">#x27;)
  plt.tight_layout()

「把天气流和报价流对齐到同一根K线」

做农业产区逻辑的交易者常踩一个坑:气象站每小时报一次温湿度,MT5 的 AUDUSD 报价却是分笔跳动。两者频率不同,直接 concat 会错位。代码里用 region_pair_mapping 把产区绑死到货币对——澳洲小麦带走 AUDUSD、新西兰坎特伯雷走 NZDUSD、加拿大草原走 USDCAD,三个对子各拉 H1/H4/D1 三周期,再用 merge_asof 按时间戳向后就近对齐,避免天气滞后被硬塞进错的那根 bar。 衍生特征这块,rolling(24) 算的是过去 24 根的价格波动率 std,temp_change 用 diff() 取温差,precip_intensity 是 24 根降水滚动求和;另外二值化的作物生长季字段对农产品季节性波动有区分度,建议在 MT5 导出的 csv 里自己加一列验证。 清洗规矩很硬:天气列 ffill 只许填 3 个周期,再多就当断流而非暂缺;温度降水风速按 1% 和 99% 分位截尾,超出范围的样本直接 drop。外汇与贵金属本身高杠杆高风险,这类跨源特征只能作为概率参考,不能单独用作开仓依据。 开 MT5 把上面三个对子调出 H1 图,手动比对气象 API 的整点时间戳,能立刻看出 merge_asof 对齐是否吞掉了跳空段。

MQL5 / C++
def get_agricultural_forex_pairs():
    """
    Getting data on currency pairs via MetaTrader class="num">5
    """
    if not mt5.initialize():
        print("MT5 initialization error")
        class="kw">return None
    
    pairs = ["AUDUSD", "NZDUSD", "USDCAD"]
    timeframes = {
        "H1": mt5.TIMEFRAME_H1,
        "H4": mt5.TIMEFRAME_H4,
        "D1": mt5.TIMEFRAME_D1
    }
    # ... the rest of the function code
def merge_weather_forex_data(weather_data, forex_data):
    """
    Combining weather and financial data
    """
    synchronized_data = {}
    
    region_pair_mapping = {
        &class="macro">#x27;AU_WheatBelt&class="macro">#x27;: &class="macro">#x27;AUDUSD&class="macro">#x27;,
        &class="macro">#x27;NZ_Canterbury&class="macro">#x27;: &class="macro">#x27;NZDUSD&class="macro">#x27;,
        &class="macro">#x27;CA_Prairies&class="macro">#x27;: &class="macro">#x27;USDCAD&class="macro">#x27;
    }
    # ... the rest of the function code
def calculate_derived_features(data):
    """
    Calculation of derived indicators
    """
    if not data.empty:
        data[&class="macro">#x27;price_volatility&class="macro">#x27;] = data[&class="macro">#x27;volatility&class="macro">#x27;].rolling(class="num">24).std()
        data[&class="macro">#x27;temp_change&class="macro">#x27;] = data[&class="macro">#x27;temperature&class="macro">#x27;].diff()
        data[&class="macro">#x27;precip_intensity&class="macro">#x27;] = data[&class="macro">#x27;precipitation&class="macro">#x27;].rolling(class="num">24).sum()
        # ... the rest of the function code
def clean_merged_data(data):
    """
    Cleaning up merged data
    """
    weather_cols = [&class="macro">#x27;temperature&class="macro">#x27;, &class="macro">#x27;precipitation&class="macro">#x27;, &class="macro">#x27;wind_speed&class="macro">#x27;]
    
    # Fill in the blanks
    for col in weather_cols:
        if col in data.columns:
            data[col] = data[col].ffill(limit=class="num">3)
    
    # Removing outliers
    for col in weather_cols:
        if col in data.columns:
            q_low = data[col].quantile(class="num">0.01)
            q_high = data[col].quantile(class="num">0.99)
            data = data[
                (data[col] > q_low) & 
                (data[col] < q_high)
            ]
    # ... the rest of the function code

常见问题

可用 requests 或专门气象 API 客户端抓取产区逐日温度、降水字段,存为 DataFrame 后与对应货币对日线收益率做合并。
滞后相关性扫描常见在 3~10 个交易日出现峰值,具体品种需用滚动窗口逐一验证,不能默认统一天数。
小布可接入气象与报价流,自动对齐到同一时间轴并标记异常积温日,你直接看预警即可省去自己写脚本。
天气对交易者手感与头寸是渐进式影响,价格常滞后反应,且会被宏观数据掩盖,需看积温偏离度而非单日天气。
气象数据多用 UTC 或当地时区,报价是经纪商服务器时间,必须先统一为同一时区再按柱对齐,否则会错配。