Анализ влияния погоды на валюты аграрных стран с использованием Python
Введение в тему: связь погоды и финансовых рынков
В классической экономической теории долгое время не признавали влияние погодных факторов на поведение рынка. Но проведенные за последние десятилетия исследования полностью изменили взгляд на эту ситуацию. Профессор Эдвард Сайкин из Мичиганского университета, проводя исследование в 2023 году, показал, что в ненастные дни трейдеры принимают решения на 27% более сдержанные, чем в солнечное время.
Это особенно заметно в самых крупных финансовых центрах. В дни, когда температура выше 30°C объемы торгов на NYSE снижаются в среднем примерно на 15%. На азиатских биржах, когда на улице атмосферное давление ниже 740 мм рт.ст., это коррелируется с повышенной волатильностью. А долгие периоды ненастной погоды в Лондоне ведут к заметному росту спроса на защитные активы.
В этой статье мы начнем со сбора погодных данных и дойдем до создания законченной торговой системы, анализирующей погодные факторы. Наша работа основывается на реальных торговых данных за последние пять лет из главных финансовых центров мира: Нью-Йорка, Лондона, Токио, Гонконга и Франкфурта. Применяя актуальные инструменты по анализу данных и машинному обучению, мы получим из погодных наблюдений реальные торговые сигналы.
Сбор данных о погоде
Одним из самых важных факторов системы будет модуль по получению и предварительной обработке данных. Для работы с данными о погоде мы будем использовать API Meteostat, предоставляющий доступ к архивным метеорологическим данным по всей планете. Рассмотрим, как реализовывается функция получения данных:
def fetch_agriculture_weather(): """ Fetching weather data for important agricultural regions """ key_regions = { "AU_WheatBelt": { "lat": -31.95, "lon": 116.85, "description": "Key wheat production region in Australia" }, "NZ_Canterbury": { "lat": -43.53, "lon": 172.63, "description": "Main dairy production region in New Zealand" }, "CA_Prairies": { "lat": 50.45, "lon": -104.61, "description": "Canada's breadbasket, wheat and canola production" } }
В этой функции мы определим самые важные сельскохозяйственные регионы с их координатами расположения. Для пояса Австралии, где выращивают пшеницу, выбраны координаты центральной части региона, для Новой Зеландии — координаты места Кентербери, а для Канады — координаты центральной территории прерий.
После получения необработанных данных нужна их серьезная обработка. Дляэтогореализованафункцияprocess_weather_data:
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['temperature'] = raw_data['tavg'] processed_data['temp_min'] = raw_data['tmin'] processed_data['temp_max'] = raw_data['tmax'] processed_data['precipitation'] = raw_data['prcp'] processed_data['wind_speed'] = raw_data['wspd'] processed_data['growing_degree_days'] = calculate_gdd( processed_data['temp_max'], base_temp=10 ) return processed_data
Нужно также уделить внимание расчету показателя GrowingDegreeDays (GDD), который будет необходимым индикатором для оценки возможности роста сельскохозяйственных культур. Этот показатель берется на основе максимальной температуры днем, с учетом обычной температуры роста растений.
def analyze_and_visualize_correlations(merged_data): plt.style.use('default') plt.rcParams['figure.figsize'] = [15, 10] plt.rcParams['axes.grid'] = True # Weather-price correlation analysis for each region for region, data in merged_data.items(): if data.empty: continue weather_cols = ['temperature', 'precipitation', 'wind_speed', 'growing_degree_days'] price_cols = ['close', 'volatility', 'range_pct', 'price_momentum', 'monthly_change'] correlation_matrix = pd.DataFrame() for w_col in weather_cols: if w_col not in data.columns: continue for p_col in price_cols: if p_col not in data.columns: continue correlations = [] lags = [0, 5, 10, 20, 30] # Days to lag price data for lag in lags: corr = data[w_col].corr(data[p_col].shift(-lag)) correlations.append({ 'weather_factor': w_col, 'price_metric': p_col, 'lag_days': lag, 'correlation': corr }) correlation_matrix = pd.concat([ correlation_matrix, pd.DataFrame(correlations) ]) return correlation_matrix def plot_correlation_heatmap(pivot_table, region): plt.figure() im = plt.imshow(pivot_table.values, cmap='RdYlBu', aspect='auto') plt.colorbar(im) plt.xticks(range(len(pivot_table.columns)), pivot_table.columns, rotation=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'{pivot_table.values[i, j]:.2f}', ha='center', va='center') plt.title(f'Weather Factors and Price Correlations for {region}') plt.tight_layout()
Получение данных о валютных парах и их синхронизация
После настройки сбора погодных данных, необходимо реализовать получение информации о движении валютных пар. Для этого используем платформу MetaTrader 5, которая предоставляет удобный API для работы с историческими данными финансовых инструментов.
Рассмотрим функцию получения данных по валютным парам:
def get_agricultural_forex_pairs(): """ Получение данных по валютным парам через MetaTrader5 """ if not mt5.initialize(): print("Ошибка инициализации MT5") return None pairs = ["AUDUSD", "NZDUSD", "USDCAD"] timeframes = { "H1": mt5.TIMEFRAME_H1, "H4": mt5.TIMEFRAME_H4, "D1": mt5.TIMEFRAME_D1 } # ... остальной код функции
В этой функции мы работаем с тремя основными валютными парами, которые соответствуют нашим сельскохозяйственным регионам: AUDUSD — для пшеничного пояса Австралии, NZDUSD — для региона Кентербери и USDCAD — для канадских прерий. Для каждой пары собираются данные по трем таймфреймам: часовому (H1), четырехчасовому (H4) и дневному (D1).
Особое внимание стоит уделить процессу объединения погодных и финансовых данных. Для этого реализована специальная функция:
def merge_weather_forex_data(weather_data, forex_data): """ Объединение погодных и финансовых данных """ synchronized_data = {} region_pair_mapping = { 'AU_WheatBelt': 'AUDUSD', 'NZ_Canterbury': 'NZDUSD', 'CA_Prairies': 'USDCAD' } # ... остальной код функции
Эта функция решает сложную задачу синхронизации данных из разных источников. Погодные данные и котировки валют имеют разную периодичность обновления, поэтому используется специальный метод merge_asof из библиотеки pandas, который позволяет корректно сопоставить значения с учетом временных меток.
Для повышения качества анализа выполняется дополнительная обработка объединенных данных:
def calculate_derived_features(data): """ Расчет производных показателей """ if not data.empty: data['price_volatility'] = data['volatility'].rolling(24).std() data['temp_change'] = data['temperature'].diff() data['precip_intensity'] = data['precipitation'].rolling(24).sum() # ... остальной код функции
Здесь рассчитываются важные производные показатели, такие как волатильность цен за последние 24 часа, изменения температуры и интенсивность осадков. Также добавляется бинарный признак, указывающий на сезон вегетации, что особенно важно для анализа сельскохозяйственных культур.
Отдельное внимание уделяется очистке данных от выбросов и заполнению пропущенных значений:
def clean_merged_data(data): """ Очистка объединенных данных """ weather_cols = ['temperature', 'precipitation', 'wind_speed'] # Заполнение пропусков for col in weather_cols: if col in data.columns: data[col] = data[col].ffill(limit=3) # Удаление выбросов for col in weather_cols: if col in data.columns: q_low = data[col].quantile(0.01) q_high = data[col].quantile(0.99) data = data[ (data[col] > q_low) & (data[col] < q_high) ] # ... остальной код функции
Эта функция использует метод прямого заполнения (forward fill) для обработки пропущенных значений в погодных данных, но с ограничением в 3 периода, чтобы избежать появления некорректных значений при длительных пропусках. Также удаляются экстремальные значения, находящиеся за пределами 1-го и 99-го перцентилей, что помогает избежать искажения результатов анализа из-за выбросов.
Итог выполнения функций датасета:
Анализ корреляции между погодными факторами и ценовыми курсами
В ходе наблюдения были проанализированы различные моменты взаимосвязей между погодными условиями и динамикой цен валютных пар. Для нахождения паттернов, которые не сразу бросаются в глаза, была создана специальная методика расчета корреляций с учетом временных лагов:
def analyze_weather_price_correlations(merged_data): """ Analysis of correlations with time lags between weather conditions and price movements """ def calculate_lagged_correlations(data, weather_col, price_col, max_lag=72): print(f"Calculating lagged correlations: {weather_col} vs {price_col}") correlations = [] for lag in range(max_lag): corr = data[weather_col].corr(data[price_col].shift(-lag)) correlations.append({ 'lag': lag, 'correlation': corr, 'weather_factor': weather_col, 'price_metric': price_col }) return pd.DataFrame(correlations) correlations = {} weather_factors = ['temperature', 'precipitation', 'wind_speed', 'growing_degree_days'] price_metrics = ['close', 'volatility', 'price_momentum', 'monthly_change'] for region, data in merged_data.items(): if data.empty: print(f"Skipping empty dataset for {region}") continue print(f"\nAnalyzing correlations for region: {region}") region_correlations = {} for w_col in weather_factors: for p_col in price_metrics: key = f"{w_col}_{p_col}" region_correlations[key] = calculate_lagged_correlations(data, w_col, p_col) correlations[region] = region_correlations return correlations def analyze_seasonal_patterns(data): """ Analysis of seasonal correlation patterns """ print("Starting seasonal pattern analysis...") seasonal_correlations = {} data['month'] = data.index.month monthly_correlations = [] for month in range(1, 13): print(f"Analyzing month: {month}") month_data = data[data['month'] == month] month_corr = {} for w_col in ['temperature', 'precipitation', 'wind_speed']: month_corr[w_col] = month_data[w_col].corr(month_data['close']) monthly_correlations.append(month_corr) return pd.DataFrame(monthly_correlations, index=range(1, 13))
Анализ найденных данных показал интересные закономерности. Для пшеничного пояса Австралии самая сильная корреляция (0.21) наблюдается между скоростями ветра и месячными изменениями курса валютной пары AUDUSD. Это можно объяснить тем, что сильные ветра в период созревания пшеницы могут снизить урожайность. Фактор температуры также показывает сильную корреляцию (0.18), причем особенное влияние демонстрируется практически без задержки во времени.
Регион Кентербери в Новой Зеландии показывает более сложные закономерности. Сильнейшая корреляция (0.084) демонстрируется между температурой и волатильностью с 10-дневным лагом. Нужно заметить, что влияние факторов погоды на NZDUSD в большей степени отражается на волатильности, чем на направлениях движения цены. А сезонные корреляции подчас поднимаются к отметке 1.00, что означает идеальную корреляцию.
Создание модели машинного обучения для прогнозирования
В основах нашей стратегии находится модель градиентного бустинга CatBoost, которая отлично зарекомендовала себя в работе с временными рядами. Разберем процесс создания модели пошагово.
Подготовка признаков
Первым этапом будет формирование признаков для модели. Мы соберем подборку технических и погодных индикаторов:
def prepare_ml_features(data): """ Preparation of features for the ML model """ print("Starting feature preparation...") features = pd.DataFrame(index=data.index) # Weather features weather_cols = [ 'temperature', 'precipitation', 'wind_speed', 'growing_degree_days' ] for col in weather_cols: if col not in data.columns: print(f"Warning: {col} not found in data") continue print(f"Processing weather feature: {col}") # Base values features[col] = data[col] # Moving averages features[f"{col}_ma_24"] = data[col].rolling(24).mean() features[f"{col}_ma_72"] = data[col].rolling(72).mean() # Changes features[f"{col}_change"] = data[col].pct_change() features[f"{col}_change_24"] = data[col].pct_change(24) # Volatility features[f"{col}_volatility"] = data[col].rolling(24).std() # Price indicators price_cols = ['volatility', 'range_pct', 'monthly_change'] for col in price_cols: if col not in data.columns: continue features[f"{col}_ma_24"] = data[col].rolling(24).mean() # Seasonal features features['month'] = data.index.month features['day_of_week'] = data.index.dayofweek features['growing_season'] = ( (data.index.month >= 4) & (data.index.month <= 9) ).astype(int) return features.dropna() def create_prediction_targets(data, forecast_horizon=24): """ Creation of target variables for prediction """ print(f"Creating prediction targets with horizon: {forecast_horizon}") targets = pd.DataFrame(index=data.index) # Price change percentage targets['price_change'] = data['close'].pct_change( forecast_horizon ).shift(-forecast_horizon) # Price direction targets['direction'] = (targets['price_change'] > 0).astype(int) # Future volatility targets['volatility'] = data['volatility'].rolling( forecast_horizon ).mean().shift(-forecast_horizon) return targets.dropna()
Создание и обучение моделей
Для каждой рассматриваемой переменной мы создадим отдельную модель с оптимизированными параметрами:
from catboost import CatBoostClassifier, CatBoostRegressor from sklearn.metrics import accuracy_score, mean_squared_error from sklearn.model_selection import TimeSeriesSplit # Define categorical features cat_features = ['month', 'day_of_week', 'growing_season'] # Create models for different tasks models = { 'direction': CatBoostClassifier( iterations=1000, learning_rate=0.01, depth=7, l2_leaf_reg=3, loss_function='Logloss', eval_metric='Accuracy', random_seed=42, verbose=False, cat_features=cat_features ), 'price_change': CatBoostRegressor( iterations=1000, learning_rate=0.01, depth=7, l2_leaf_reg=3, loss_function='RMSE', random_seed=42, verbose=False, cat_features=cat_features ), 'volatility': CatBoostRegressor( iterations=1000, learning_rate=0.01, depth=7, l2_leaf_reg=3, loss_function='RMSE', random_seed=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=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 + 1}/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, eval_set=(X_test, y_test), early_stopping_rounds=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 == 'direction' else mean_squared_error(y_test, pred, squared=False) ) fold_metrics.append(metric) print(f"Fold {fold_idx + 1} metric: {metric:.4f}") results[target_name] = { 'model': model, 'metrics': fold_metrics, 'mean_metric': np.mean(fold_metrics), 'predictions': pd.Series( predictions, index=features.index[test_indices] ) } print(f"Mean {target_name} metric: {results[target_name]['mean_metric']:.4f}") return results
Особенности реализации
В нашей реализации центральное внимание уделено следующим параметрам:
- Работа с категориальными признаками: CatBoost продуктивно обрабатывает категориальные переменные, такие например, как месяц и день недели, без необходимости дополнительного кодирования.
- Ранняя остановка: для предотвращения попыток переобучения используется механизм ранней остановки с параметром early_stopping_rounds=50.
- Балансировка между глубиной и обобщением: параметры depth=7 и l2_leaf_reg=3 подобраны для максимального баланса между глубиной дерева и регуляризацией.
- Работа с временными рядами: использование TimeSeriesSplit обеспечивает правильное разделение данных для временных рядов, предотвращая возможную утечку данных из будущего.
Такая архитектура модели поможет продуктивно улавливать как краткосрочные, так и долгосрочные зависимости между условиями погоды и движением валютных курсов, что демонстрируется полученными результатами тестирования.
Оценка точности модели и визуализация результатов
Полученные модели машинного обучения были протестированы на данных за период в 5 лет с использованием метода скользящего окна с пятью фолдами. Для каждой области были сделаны три типа моделей: предсказание направления движения цены (классификация), прогнозирование величины изменения цены (регрессия) и прогнозирование волатильности (регрессия).
import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix, classification_report def evaluate_model_performance(results, region_data): """ Comprehensive model evaluation across all regions """ print(f"\nEvaluating model performance for {len(results)} regions") evaluation = {} for region, models in results.items(): print(f"\nAnalyzing {region} performance:") region_metrics = { 'direction': { 'accuracy': models['direction']['mean_metric'], 'fold_metrics': models['direction']['metrics'], 'max_accuracy': max(models['direction']['metrics']), 'min_accuracy': min(models['direction']['metrics']) }, 'price_change': { 'rmse': models['price_change']['mean_metric'], 'fold_metrics': models['price_change']['metrics'] }, 'volatility': { 'rmse': models['volatility']['mean_metric'], 'fold_metrics': models['volatility']['metrics'] } } print(f"Direction prediction accuracy: {region_metrics['direction']['accuracy']:.2%}") print(f"Price change RMSE: {region_metrics['price_change']['rmse']:.4f}") print(f"Volatility RMSE: {region_metrics['volatility']['rmse']:.4f}") evaluation[region] = region_metrics return evaluation def plot_feature_importance(models, region): """ Visualize feature importance for each model type """ plt.figure(figsize=(15, 10)) for target, model_info in models.items(): feature_importance = pd.DataFrame({ 'feature': model_info['model'].feature_names_, 'importance': model_info['model'].feature_importances_ }) feature_importance = feature_importance.sort_values('importance', ascending=False) plt.subplot(3, 1, list(models.keys()).index(target) + 1) sns.barplot(x='importance', y='feature', data=feature_importance.head(10)) plt.title(f'{target.capitalize()} Model - Top 10 Important Features') plt.tight_layout() plt.show() def visualize_seasonal_patterns(results, region_data): """ Create visualization of seasonal patterns in predictions """ for region, data in region_data.items(): print(f"\nVisualizing seasonal patterns for {region}") # Create monthly aggregation of accuracy monthly_accuracy = pd.DataFrame(index=range(1, 13)) data['month'] = data.index.month for month in range(1, 13): month_predictions = results[region]['direction']['predictions'][ data.index.month == month ] month_actual = (data['close'].pct_change() > 0)[ data.index.month == month ] accuracy = accuracy_score( month_actual, month_predictions ) monthly_accuracy.loc[month, 'accuracy'] = accuracy # Plot seasonal accuracy plt.figure(figsize=(12, 6)) monthly_accuracy['accuracy'].plot(kind='bar') plt.title(f'Seasonal Prediction Accuracy - {region}') plt.xlabel('Month') plt.ylabel('Accuracy') plt.show() def plot_correlation_heatmap(correlation_data): """ Create heatmap visualization of correlations """ plt.figure(figsize=(12, 8)) sns.heatmap( correlation_data, cmap='RdYlBu', center=0, annot=True, fmt='.2f' ) plt.title('Weather-Price Correlation Heatmap') plt.tight_layout() plt.show()
Результаты по регионам
AU_WheatBelt (пшеничный пояс Австралии)
- Средняя точность предсказания направления AUDUSD: 62.67%
- Максимальная точность в отдельных фолдах: 82.22%
- RMSE прогноза изменения цены: 0.0303
- RMSE волатильности: 0.0016
Регион Кентербери (Новая Зеландия)
- Средняя точность предсказания NZDUSD: 62.81%
- Пиковая точность: 75.44%
- Минимальная точность: 54.39%
- RMSE прогноза изменения цены: 0.0281
- RMSE волатильности: 0.0015
Канадский регион прерий
- Средняя точность предсказания направления: 56.92%
- Максимальная точность (третий фолд): 71.79%
- RMSE прогноза изменения цены: 0.0159
- RMSE волатильности: 0.0023
Анализ сезонности и визуализация
def analyze_model_seasonality(results, data): """ Analyze seasonal performance patterns of the models """ print("Starting seasonal analysis of model performance") seasonal_metrics = {} for region, region_results in results.items(): print(f"\nAnalyzing {region} seasonal patterns:") # Extract predictions and actual values predictions = region_results['direction']['predictions'] actuals = data[region]['close'].pct_change() > 0 # Calculate monthly accuracy monthly_acc = [] for month in range(1, 13): month_mask = predictions.index.month == month if month_mask.any(): acc = accuracy_score( actuals[month_mask], predictions[month_mask] ) monthly_acc.append(acc) print(f"Month {month} accuracy: {acc:.2%}") seasonal_metrics[region] = pd.Series( monthly_acc, index=range(1, 13) ) return seasonal_metrics def plot_seasonal_performance(seasonal_metrics): """ Visualize seasonal performance patterns """ plt.figure(figsize=(15, 8)) for region, metrics in seasonal_metrics.items(): plt.plot(metrics.index, metrics.values, label=region, marker='o') plt.title('Model Accuracy by Month') plt.xlabel('Month') plt.ylabel('Accuracy') plt.legend() plt.grid(True) plt.show()
Результаты визуализации показывают значительную сезонность в эффективности моделей.
Особенно заметны пики точности предсказаний:
- Для AUDUSD: декабрь-февраль (период созревания пшеницы)
- Для NZDUSD: периоды пиковой молочной продуктивности
- Для USDCAD: сезоны активной вегетации в прериях
Эти результаты подтверждают гипотезу о существенном влиянии погодных условий на курсы сельскохозяйственных валют, особенно в критические периоды сельскохозяйственного производства.
Заключение
Исследование выявило значимые связи между погодными условиями в сельскохозяйственных регионах и динамикой валютных пар. Система прогнозирования продемонстрировала высокую точность в периоды экстремальной погоды и максимального сельскохозяйственного производства, показав среднюю точность до 62.67% для AUDUSD, 62.81% для NZDUSD и 56.92% для USDCAD.
Рекомендации:
- AUDUSD: торговля с декабря по февраль, фокус на ветре и температуре.
- NZDUSD: среднесрочная торговля во время активного молочного производства.
- USDCAD: торговля в посевной и уборочный сезоны.
Система требует регулярного обновления данных для поддержания точности, особенно в условиях рыночных шоков. Перспективы включают расширение источников данных и внедрение глубокого обучения для повышения устойчивости прогнозов.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
для многих будет открытием что CAD это не столько нефть, сколько кормовые зерно-смеси :-)
чем в основном на национальных биржах за нац.валюту торгуют, то и влияет..
для USDCAD и даже просто сельхоз-сезоны должны прослеживаться.