使用 Python 分析天气对农业国家货币的影响·进阶篇
澳新产区天气怎么牵动澳纽系货币
把天气当宏观因子喂进汇率模型,最麻烦的是传导不即时。我们改用带时间滞后的相关性算法,把风速、温度等气象序列对价格做错位对齐,才挖出几条不直观的链路。 澳大利亚小麦带的数据最干净:风速和 AUDUSD 月度汇率变化的相关性冲到 0.21,为所有因子之最。逻辑上说得通——小麦成熟期刮大风会砸产量,出口预期走弱压制澳元。温度因子相关性 0.18,且几乎零滞后,说明当地气温对澳元是近同步冲击。 新西兰坎特伯雷完全是另一套节奏。温度与 NZDUSD 波动率的最强相关只有 0.084,却出现在 10 天滞后上;天气对纽元更多搅动波动而非定向推价。季节性切片里,某些月份天气与收盘价的相关系数能摸到 1.00,属完美同步,但样本极薄,不能当常态用。外汇与贵金属杠杆高,这类弱相关只适合做仓位微调的旁证,绝非入场主信号。 下面这段 Python 是原分析核心,思路可直接平移到 MT5 用 MQL5 重写:先按最大滞后窗口滑动计算 corr,再分地区、分气象因子扫一遍。
<span class="keyword">def</span> analyze_weather_price_correlations(merged_data): <span class="class="type">class="kw">string">""" Analysis of correlations with time lags between weather conditions and price movements """</span> <span class="keyword">def</span> calculate_lagged_correlations(data, weather_col, price_col, max_lag=<span class="number">class="num">72</span>): <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"Calculating lagged correlations: <span>{weather_col}</span> vs <span>{price_col}</span>"</span>) correlations = [] <span class="keyword">for</span> lag <span class="keyword">in</span> <span class="built_in">range</span>(max_lag): corr = data[weather_col].corr(data[price_col].shift(-lag)) correlations.append({ <span class="class="type">class="kw">string">&class="macro">#x27;lag&class="macro">#x27;</span>: lag, <span class="class="type">class="kw">string">&class="macro">#x27;correlation&class="macro">#x27;</span>: corr, <span class="class="type">class="kw">string">&class="macro">#x27;weather_factor&class="macro">#x27;</span>: weather_col, <span class="class="type">class="kw">string">&class="macro">#x27;price_metric&class="macro">#x27;</span>: price_col }) <span class="keyword">class="kw">return</span> pd.DataFrame(correlations) correlations = {} weather_factors = [<span class="class="type">class="kw">string">&class="macro">#x27;temperature&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;precipitation&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;wind_speed&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;growing_degree_days&class="macro">#x27;</span>] price_metrics = [<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;volatility&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;price_momentum&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;monthly_change&class="macro">#x27;</span>] <span class="keyword">for</span> region, data <span class="keyword">in</span> merged_data.items(): <span class="keyword">if</span> data.empty: <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"Skipping empty dataset for <span>{region}</span>"</span>) <span class="keyword">class="kw">continue</span> <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"\nAnalyzing correlations for region: <span>{region}</span>"</span>) region_correlations = {} <span class="keyword">for</span> w_col <span class="keyword">in</span> weather_factors: <span class="keyword">for</span> p_col <span class="keyword">in</span> price_metrics: key = <span class="class="type">class="kw">string">f"<span>{w_col}</span>_<span>{p_col}</span>"</span> region_correlations[key] = calculate_lagged_correlations(data, w_col, p_col) correlations[region] = region_correlations <span class="keyword">class="kw">return</span> correlations <span class="keyword">def</span> analyze_seasonal_patterns(data): <span class="class="type">class="kw">string">""" Analysis of seasonal correlation patterns """</span> <span class="built_in">print</span>(<span class="class="type">class="kw">string">"Starting seasonal pattern analysis..."</span>) seasonal_correlations = {} data[<span class="class="type">class="kw">string">&class="macro">#x27;month&class="macro">#x27;</span>] = data.index.month monthly_correlations = [] <span class="keyword">for</span> month <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">class="num">1</span>, <span class="number">class="num">13</span>): <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"Analyzing month: <span>{month}</span>"</span>) month_data = data[data[<span class="class="type">class="kw">string">&class="macro">#x27;month&class="macro">#x27;</span>] == month] month_corr = {} <span class="keyword">for</span> w_col <span class="keyword">in</span> [<span class="class="type">class="kw">string">&class="macro">#x27;temperature&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;precipitation&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;wind_speed&class="macro">#x27;</span>]: month_corr[w_col] = month_data[w_col].corr(month_data[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>]) monthly_correlations.append(month_corr)
「月度相关性矩阵的返回结构」
这段 Python 收尾语句把前面算出的 12 个月相关性结果直接压成一个 DataFrame 返回,行索引用 range(1,13) 对应 1 到 12 月。 在 MT5 侧做等效处理时,你可以用二维数组按月份存相关系数,再用 CopyBuffer 或文件写出做后续检验;该结构便于横向比对黄金与美指在每年不同月份的联动强弱。 注意外汇与贵金属属高风险品种,历史月度相关不预示未来,开 MT5 跑一遍自己的样本周期更稳妥。
class="kw">return pd.DataFrame(monthly_correlations, index=range(class="num">1, class="num">13))
◍ 用 CatBoost 搭天气-汇率预测骨架
做汇率与贵金属联动预测时,CatBoost 这类梯度提升树比传统时序模型更省事:它原生吃类别变量,月份、星期几直接丢进去不用独热编码。我们实测在 1 小时级数据上,用 depth=7 配合 l2_leaf_reg=3,能在树复杂度和泛化之间拉出一个较稳的平衡点。 防过拟合别靠拍脑袋,early_stopping_rounds=50 是底线配置。数据切分必须用 TimeSeriesSplit,否则回测里混进未来信息,外汇这种高杠杆品种上会给出完全虚假的胜率倾向。 特征侧除了温度、降水、风速、积温四类天气字段,还叠了 24 期与 72 期滑动均值、百分比变化、24 期波动率;价格侧取波动率、振幅占比、月度变化的 24 期均。季节哑变量用 4—9 月标记生长季,编码为 0/1。下面这段 Python 函数可直接拷去跑特征工程。 别把正态当圣经 天气因子和汇率收益都不是高斯分布,用 pct_change 前先画个直方图,否则 CatBoost 也会在厚尾区给出偏激的叶子权重。
<span class="keyword">def</span> prepare_ml_features(data): <span class="class="type">class="kw">string">""" Preparation of features for the ML model """</span> <span class="built_in">print</span>(<span class="class="type">class="kw">string">"Starting feature preparation..."</span>) features = pd.DataFrame(index=data.index) <span class="comment"># Weather features</span> weather_cols = [ <span class="class="type">class="kw">string">&class="macro">#x27;temperature&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;precipitation&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;wind_speed&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;growing_degree_days&class="macro">#x27;</span> ] <span class="keyword">for</span> col <span class="keyword">in</span> weather_cols: <span class="keyword">if</span> col <span class="keyword">not</span> <span class="keyword">in</span> data.columns: <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"Warning: <span>{col}</span> not found in data"</span>) <span class="keyword">class="kw">continue</span> <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"Processing weather feature: <span>{col}</span>"</span>) <span class="comment"># Base values</span> features[col] = data[col] <span class="comment"># Moving averages</span> features[<span class="class="type">class="kw">string">f"<span>{col}</span>_ma_24"</span>] = data[col].rolling(<span class="number">class="num">24</span>).mean() features[<span class="class="type">class="kw">string">f"<span>{col}</span>_ma_72"</span>] = data[col].rolling(<span class="number">class="num">72</span>).mean() <span class="comment"># Changes</span> features[<span class="class="type">class="kw">string">f"<span>{col}</span>_change"</span>] = data[col].pct_change() features[<span class="class="type">class="kw">string">f"<span>{col}</span>_change_24"</span>] = data[col].pct_change(<span class="number">class="num">24</span>) <span class="comment"># Volatility</span> features[<span class="class="type">class="kw">string">f"<span>{col}</span>_volatility"</span>] = data[col].rolling(<span class="number">class="num">24</span>).std() <span class="comment"># Price indicators</span> price_cols = [<span class="class="type">class="kw">string">&class="macro">#x27;volatility&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;range_pct&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;monthly_change&class="macro">#x27;</span>] <span class="keyword">for</span> col <span class="keyword">in</span> price_cols: <span class="keyword">if</span> col <span class="keyword">not</span> <span class="keyword">in</span> data.columns: <span class="keyword">class="kw">continue</span> features[<span class="class="type">class="kw">string">f"<span>{col}</span>_ma_24"</span>] = data[col].rolling(<span class="number">class="num">24</span>).mean() <span class="comment"># Seasonal features</span> features[<span class="class="type">class="kw">string">&class="macro">#x27;month&class="macro">#x27;</span>] = data.index.month features[<span class="class="type">class="kw">string">&class="macro">#x27;day_of_week&class="macro">#x27;</span>] = data.index.dayofweek features[<span class="class="type">class="kw">string">&class="macro">#x27;growing_season&class="macro">#x27;</span>] = ( (data.index.month >= <span class="number">class="num">4</span>) & (data.index.month <= <span class="number">class="num">9</span>) ).astype(<span class="built_in">class="type">int</span>) <span class="keyword">class="kw">return</span> features.dropna() <span class="keyword">def</span> create_prediction_targets(data, forecast_horizon=<span class="number">class="num">24</span>): <span class="class="type">class="kw">string">""" Creation of target variables for prediction """</span> <span class="built_in">print</span>(<span class="class="type">class="kw">string">f"Creating prediction targets with horizon: <span>{forecast_horizon}</span>"</span>) targets = pd.DataFrame(index=data.index) <span class="comment"># Price change percentage</span> targets[<span class="class="type">class="kw">string">&class="macro">#x27;price_change&class="macro">#x27;</span>] = data[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].pct_change( forecast_horizon ).shift(-forecast_horizon) <span class="comment"># Price direction</span> targets[<span class="class="type">class="kw">string">&class="macro">#x27;direction&class="macro">#x27;</span>] = (targets[<span class="class="type">class="kw">string">&class="macro">#x27;price_change&class="macro">#x27;</span>] > <span class="number">class="num">0</span>).astype(<span class="built_in">class="type">int</span>) <span class="comment"># Future volatility</span>
用时序交叉验证训练三类预测模型
这段逻辑把波动率目标往前平移 forecast_horizon 根 K 线再取滚动均值,等于让模型去猜未来一段窗口的平均波动,而不是单根收盘价。targets.dropna() 会砍掉末尾因平移产生的空值,实际可用样本量比原始数据少一个 horizon,回测前得先确认样本够不够分 5 折。 模型组里 direction 用 CatBoostClassifier 跑 Logloss,price_change 和 volatility 都用 Regressor 跑 RMSE,三个模型迭代都是 1000 轮、学习率 0.01、深度 7、L2 叶子正则 3,随机种子锁死 42 才能保证复现。类别特征只塞了 month、day_of_week、grow_season 三个,做贵金属季节性扰动时这类字段比裸价格有用。 train_ml_models 里用 TimeSeriesSplit(n_splits=5) 而不是随机切,避免用未来泄密训练集。每折只 print 进度,真正拟合时靠 early stopping 控过拟合——外汇和贵金属波动受宏观事件驱动,不按时序切折,回测漂亮实盘大概率崩。 开 MT5 把这段 Python 逻辑改写成 MQL5 的 CTrade+自定义指标前,先拿 EURUSD 的 H1 跑通 5 折,看 volatility 模型的 RMSE 是否随折数递增,若第 5 折误差突增,说明近期结构断裂,得缩短训练窗口。
targets[&class="macro">#x27;volatility&class="macro">#x27;] = data[&class="macro">#x27;volatility&class="macro">#x27;].rolling( forecast_horizon ).mean().shift(-forecast_horizon) class="kw">return targets.dropna() from catboost class="kw">import CatBoostClassifier, CatBoostRegressor from sklearn.metrics class="kw">import accuracy_score, mean_squared_error from sklearn.model_selection class="kw">import TimeSeriesSplit # Define categorical features cat_features = [&class="macro">#x27;month&class="macro">#x27;, &class="macro">#x27;day_of_week&class="macro">#x27;, &class="macro">#x27;growing_season&class="macro">#x27;] # Create models for different tasks models = { &class="macro">#x27;direction&class="macro">#x27;: CatBoostClassifier( iterations=class="num">1000, learning_rate=class="num">0.01, depth=class="num">7, l2_leaf_reg=class="num">3, loss_function=&class="macro">#x27;Logloss&class="macro">#x27;, eval_metric=&class="macro">#x27;Accuracy&class="macro">#x27;, random_seed=class="num">42, verbose=False, cat_features=cat_features ), &class="macro">#x27;price_change&class="macro">#x27;: CatBoostRegressor( iterations=class="num">1000, learning_rate=class="num">0.01, depth=class="num">7, l2_leaf_reg=class="num">3, loss_function=&class="macro">#x27;RMSE&class="macro">#x27;, random_seed=class="num">42, verbose=False, cat_features=cat_features ), &class="macro">#x27;volatility&class="macro">#x27;: CatBoostRegressor( iterations=class="num">1000, learning_rate=class="num">0.01, depth=class="num">7, l2_leaf_reg=class="num">3, loss_function=&class="macro">#x27;RMSE&class="macro">#x27;, random_seed=class="num">42, verbose=False, cat_features=cat_features ) } def train_ml_models(merged_data, region): """ Training ML models using time series cross-validation """ print(f"Starting model training for region: {region}") data = merged_data[region] features = prepare_ml_features(data) targets = create_prediction_targets(data) # Split into folds tscv = TimeSeriesSplit(n_splits=class="num">5) results = {} for target_name, model in models.items(): print(f"\nTraining model for target: {target_name}") fold_metrics = [] predictions = [] test_indices = [] for fold_idx, (train_idx, test_idx) in enumerate(tscv.split(features)): print(f"Processing fold {fold_idx + class="num">1}/class="num">5") X_train = features.iloc[train_idx] y_train = targets[target_name].iloc[train_idx] X_test = features.iloc[test_idx] y_test = targets[target_name].iloc[test_idx] # Training with early stopping model.fit( X_train, y_train,
「交叉验证里的预测与误差统计」
这段代码接在前面 K 折切分之后,负责在每一折上跑出预测并就地算指标。方向类目标用 accuracy_score,数值类目标用均方根误差(mean_squared_error 带 squared=False),两种度量在循环里靠 target_name 切换。 early_stopping_rounds=50 意味着验证集连续 50 轮没提升就停训,能压住过拟合,但折数多时总耗时会明显拉长。 每折打印 f"Fold {fold_idx+1} metric: {metric:.4f}",最后用 np.mean(fold_metrics) 汇总。predictions 按 features.index[test_indices] 对齐,避免乱序导致信号错位。外汇与贵金属波动大、杠杆高,这类模型输出只作概率参考,实盘前务必在 MT5 历史数据上重跑验证。
eval_set=(X_test, y_test),
early_stopping_rounds=class="num">50,
verbose=False
)
# Predictions and evaluation
pred = model.predict(X_test)
predictions.extend(pred)
test_indices.extend(test_idx)
# Metric calculation
metric = (
accuracy_score(y_test, pred)
if target_name == &class="macro">#x27;direction&class="macro">#x27;
else mean_squared_error(y_test, pred, squared=False)
)
fold_metrics.append(metric)
print(f"Fold {fold_idx + class="num">1} metric: {metric:.4f}")
results[target_name] = {
&class="macro">#x27;model&class="macro">#x27;: model,
&class="macro">#x27;metrics&class="macro">#x27;: fold_metrics,
&class="macro">#x27;mean_metric&class="macro">#x27;: np.mean(fold_metrics),
&class="macro">#x27;predictions&class="macro">#x27;: pd.Series(
predictions,
index=features.index[test_indices]
)
}
print(f"Mean {target_name} metric: {results[target_name][&class="macro">#x27;mean_metric&class="macro">#x27;]:.4f}")
class="kw">return results