Блог им. StanislavDzyuba

Торговля EURUSD с использованием фундаментальных факторов и ML

Всем привет, ранее я предпринимал попытки оценит обменный курс, используя данные о процентных ставках, темпах инфляции, ВВП и безработице на очень небольшом наборе данных и теперь я расширю это ислледование создав регрессионную модель используя больший объем данных, покажу откуда можно эти данные взять и как протестировать подобного рода стратегию с помощью библиотеки BT, и вы увидите результаты такой торговли.

Теория

Как вы, возможно, догадались, я исхожу из предположения, что процентная ставка влияет на валюту, потому что, во-первых, это механизм центральных банков, чтобы сообщать вам, дешево или дорого печатать деньги сейчас. Я имею в виду, что если у вас есть депозитная ставка в банке 20%, а торговля на фондовом рынке дает вам 10% годовых — вам лучше пойти в банк. Так, если сейчас ставка Банка Англии 4,25, а ставка ЕС 2,15, стоит продать евро, купить фунты и открыть депозит в Великобритании. Но процентная ставка устанавливается центральными банками, и центральные банки принимают свои решения, основываясь на макроэкономических данных, таких как — инфляция, безработица, ВВП. В основном их главная цель — поддерживать устойчивый рост (темпы роста ВВП должны неуклонно расти). Вот почему все эти факторы имеют значение, и, конечно же, центральные банки используют эти данные по-разному, они используют математические модели (похожие на IS-LM) для принятия решений о политике ставок. Что касается этого исследования, я сосредоточился на процентной ставке, инфляции, ВВП и безработице, и меня не особо интересует, как они связаны друг с другом. Я не буду рассматривать торговый баланс, баланс банка и доходность казначейских облигаций — они могут стать частью моего следующего исследования. В этом исследовании не учитываются анализ сентимента, Risk On и Risk Off.

Данные

Я получаю данные с forexfactory, просто собираю их из консоли разработчика. Возможно, есть более элегантный способ. Теперь я знаю, что investing.com дает возможность скачивать данные напрямую с сайта либо через парсер на питоне, но было уже слишком поздно, когда я об этом узнал.

Как выглядят данные при открытии графика на forexfactory.com


Я сохранил все серии в формате JSON и смог прочитать данные из JSON в Pandas датафрейм.

Json с макроданными выглядит так.


Чтение из json делается довольно просто, выглядит это примерно так:

<code>def get_inflation_rates(country: str) -> list:
    path:str = os.getcwd() + "/inflation.json"
    with open(path, 'r') as j:
        data:list = json.load(j)
        interest_rates = data[country]
        df_ir = pd.DataFrame.from_records(interest_rates)
        return df_ir</code>

После получения данных я активно объединяю их в один датафрейм и скачиваю ежедневные цены с помощью AlphaVantage. Я знаю, что тут много копипасты но мне просто лень заниматься рефакторингом:

<code>import yfinance as yf
from alpha_vantage.foreignexchange import ForeignExchange
api_key = "YOUR_API_KEY"
fx = ForeignExchange(key=api_key, output_format='pandas')
def prepare_data_for_regression(first_currency:str,second_currency:str):

    #FIRST CURRENCY
    #interest rate
    df_ir_f_currency = get_interest_rates(first_currency)
    format_date_reg(df_ir_f_currency)
    df_ir_f_currency = df_ir_f_currency.set_index('date')
    df_ir_f_currency_f = df_ir_f_currency[['actual']]
    df_ir_f_currency_f = df_ir_f_currency_f.rename(columns={'actual':'fcurr_ir'})

    #inflation rate
    df_inf_f_currency = get_inflation_rates(first_currency)
    format_date_reg(df_inf_f_currency)
    df_inf_f_currency = df_inf_f_currency.set_index('date')
    df_inf_f_currency_f = df_inf_f_currency[['actual']]
    df_inf_f_currency_f = df_inf_f_currency_f.rename(columns={'actual':'fcurr_inf'})

    #unemployment
    df_u_f_currency = get_unemployment_rates(first_currency)
    format_date_reg(df_u_f_currency)
    df_u_f_currency = df_u_f_currency.set_index('date')
    df_u_f_currency_f = df_u_f_currency[['actual']]
    df_u_f_currency_f = df_u_f_currency_f.rename(columns={'actual':'fcurr_unem'})

    #GDP
    df_gdp_f_currency = get_gdp_rates(first_currency)
    format_date_reg(df_gdp_f_currency)
    df_gdp_f_currency = df_gdp_f_currency.set_index('date')
    df_gdp_f_currency_f = df_gdp_f_currency[['actual']]
    df_gdp_f_currency_f = df_gdp_f_currency_f.rename(columns={'actual':'fcurr_gdp'})

    #SECOND CURRENCY
    #interest rate
    df_ir_s_currency = get_interest_rates(second_currency)
    format_date_reg(df_ir_s_currency)
    df_ir_s_currency = df_ir_s_currency.set_index('date')
    df_ir_s_currency_f = df_ir_s_currency[['actual']]
    df_ir_s_currency_f = df_ir_s_currency_f.rename(columns={'actual':'scurr_ir'})

    #inflation rate
    df_inf_s_currency = get_inflation_rates(second_currency)
    format_date_reg(df_inf_s_currency)
    df_inf_s_currency = df_inf_s_currency.set_index('date')
    df_inf_s_currency_f = df_inf_s_currency[['actual']]
    df_inf_s_currency_f = df_inf_s_currency_f.rename(columns={'actual':'scurr_inf'})

    #unemployment
    df_u_s_currency = get_unemployment_rates(second_currency)
    format_date_reg(df_u_s_currency)
    df_u_s_currency = df_u_s_currency.set_index('date')
    df_u_s_currency_f = df_u_s_currency[['actual']]
    df_u_s_currency_f = df_u_s_currency_f.rename(columns={'actual':'scurr_unem'})

    #GDP
    df_gdp_s_currency = get_gdp_rates(second_currency)
    format_date_reg(df_gdp_s_currency)
    df_gdp_s_currency = df_gdp_s_currency.set_index('date')
    df_gdp_s_currency_f = df_gdp_s_currency[['actual']]
    df_gdp_s_currency_f = df_gdp_s_currency_f.rename(columns={'actual':'scurr_gdp'})

    #FIRST CURRENCY MERGE
    df_ir_f_currency_f.index = pd.to_datetime(df_ir_f_currency_f.index,format="%d.%m.%Y")
    df_inf_f_currency_f.index = pd.to_datetime(df_inf_f_currency_f.index,format="%d.%m.%Y")
    df_u_f_currency_f.index = pd.to_datetime(df_u_f_currency_f.index,format="%d.%m.%Y")
    df_gdp_f_currency_f.index = pd.to_datetime(df_gdp_f_currency_f.index,format="%d.%m.%Y")

    ir_inf_f_currency = pd.merge_asof(df_ir_f_currency_f.sort_index(), df_inf_f_currency_f.sort_index(), left_index=True, right_index=True)
    ir_inf_f_currency.index = pd.to_datetime(ir_inf_f_currency.index,format="%d.%m.%Y")
    ir_inf_u_f_currency = pd.merge_asof(ir_inf_f_currency.sort_index(), df_u_f_currency_f.sort_index(), left_index=True, right_index=True)
    ir_inf_u_f_currency.index = pd.to_datetime(ir_inf_u_f_currency.index,format="%d.%m.%Y")
    ir_inf_u_gdp_f_currency = pd.merge_asof(ir_inf_u_f_currency.sort_index(), df_gdp_f_currency_f.sort_index(), left_index=True, right_index=True)

    #SECOND CURRENCY MERGE
    df_ir_s_currency_f.index = pd.to_datetime(df_ir_s_currency_f.index,format="%d.%m.%Y")
    df_inf_s_currency_f.index = pd.to_datetime(df_inf_s_currency_f.index,format="%d.%m.%Y")
    df_u_s_currency_f.index = pd.to_datetime(df_u_s_currency_f.index,format="%d.%m.%Y")
    df_gdp_s_currency_f.index = pd.to_datetime(df_gdp_s_currency_f.index,format="%d.%m.%Y")

    ir_inf_s_currency = pd.merge_asof(df_ir_s_currency_f.sort_index(), df_inf_s_currency_f.sort_index(), left_index=True, right_index=True)
    ir_inf_s_currency.index = pd.to_datetime(ir_inf_s_currency.index,format="%d.%m.%Y")
    ir_inf_u_s_currency = pd.merge_asof(ir_inf_s_currency.sort_index(), df_u_s_currency_f.sort_index(), left_index=True, right_index=True)
    ir_inf_u_s_currency.index = pd.to_datetime(ir_inf_u_s_currency.index,format="%d.%m.%Y")
    ir_inf_u_gdp_s_currency = pd.merge_asof(ir_inf_u_s_currency.sort_index(), df_gdp_s_currency_f.sort_index(), left_index=True, right_index=True)

    #MERGE BOTH CURRENCIES
    econ_data = pd.merge_asof(ir_inf_u_gdp_f_currency.sort_index(), ir_inf_u_gdp_s_currency.sort_index(), left_index=True, right_index=True)

    #LOAD EXCHANGE RATE
    ticker = first_currency.upper() + second_currency.upper() + '=X'
    
    rate, meta_data = fx.get_currency_exchange_daily(from_symbol=first_currency.upper(), to_symbol=second_currency.upper(), outputsize='full')
    rate = rate.rename(columns={'4. close':'exchange_rate'})
    data = pd.merge_asof(econ_data.sort_index(), rate.sort_index(),left_index=True,right_index=True)

    data = data.reset_index()
    
    return data</code>

Далее вызываем эту функцию, чтобы подготовить данные для регрессии:

<code>data= prepare_data_for_regression('eur', 'usd')</code>

Сама регрессия:

<code>import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error

# Step 3: Preprocessing
data = data.dropna()  # Drop rows with missing values


data['exchange_rate'] = data['exchange_rate'].diff(2)
data['fcurr_ir'] = data['fcurr_ir'].diff(1)
data['fcurr_inf'] = data['fcurr_inf'].diff(1)
data['fcurr_unem'] = data['fcurr_unem'].diff(1)
data['fcurr_ir'] = data['fcurr_ir'].diff(1)
data['fcurr_inf'] = data['fcurr_inf'].diff(1)
data['fcurr_unem'] = data['fcurr_unem'].diff(1)

data['scurr_ir'] = data['scurr_ir'].diff(1)
data['scurr_inf'] = data['scurr_inf'].diff(1)
data['scurr_unem'] = data['scurr_unem'].diff(1)
data['scurr_ir'] = data['scurr_ir'].diff(1)
data['scurr_inf'] = data['scurr_inf'].diff(1)
data['scurr_unem'] = data['scurr_unem'].diff(1)

data = data.dropna()  # Drop rows again after shifting

# Step 4: Feature and Target
X = data.drop(columns=['date', 'exchange_rate'])
y = data['exchange_rate']

# Step 5: Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Step 6: Train the model
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Step 7: Evaluate the model
y_pred = model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
print(f"Mean Absolute Error: {mae}")
print(f"Mean Squared Error: {mse}")

# Step 8: Plot predictions vs actual values
plt.figure(figsize=(10, 6))
plt.plot(y_test.values, label='Actual')
plt.plot(y_pred, label='Predicted')
plt.legend()
plt.title('Exchange Rate Prediction')
plt.show()

# Step 9: Feature Importance
feature_importance = pd.DataFrame({'Feature': X.columns, 'Importance': model.feature_importances_})
feature_importance = feature_importance.sort_values(by='Importance', ascending=False)
print(feature_importance)</code>

Вот качественные показатели по регрессионной модели для EURUSD:

> Mean Absolute Error: 0.04001494210526312

> Mean Squared Error: 0.0024860521025789452

EURUSD фактический и предсказанные разницы валютного курса


По сути, это уже подготовленный сигнал — если фактический курс выше прогнозируемого, это означает, что он, вероятно, снизится до прогнозируемого значения, и наоборот — классическая стратегия возврата к среднему.

Стратегия

Теперь я могу сохранить результаты регрессии в CSV-файл и использовать его в качестве сигнала с помощью библиотеки BT. Датафрейм будет выглядеть примерно так:

Торговля EURUSD с использованием фундаментальных факторов и ML

Заполнение столбца «signal» выглядит так. Я также добавил к сигналу фильтрацию по скользящей средней c 50-дневным периодом.

<code>for i in range(len(df)):
    el = df.iloc[i]
    if(el['exchange_rate']>el['predicted_signal'] and el['exchange_rate']>el['ma50']):
        df.at[i, 'signal'] = -1
    elif(el['exchange_rate']<el['predicted_signal'] and el['exchange_rate']<el['ma50']):
        df.at[i, 'signal'] = 1
    df.at[i, 'date'] = datetime.strptime(df.at[i, 'date'], "%Y-%m-%d")</code>

Вот так выглядит стратегия

<code>import bt

def run_ml_strategy(df):
    price = df[["exchange_rate"]]
    signal = df[["signal"]]
    signal = signal.rename(columns={'signal':'exchange_rate'})

    strategy = bt.Strategy(
        "MLStrategy",
        [
            bt.algos.RunDaily(),
            bt.algos.SelectAll(),
            bt.algos.WeighTarget(signal),
            bt.algos.Rebalance()
        ]
    )

    backtest = bt.Backtest(strategy, price)
    return bt.run(backtest)

result = run_ml_strategy(df)
result.plot(title="ML Strategy - Equity Curv</code>

Результат:

EURUSD кривая капитала

Получим больше результатов:

<code>result.display()</code>
<code>Stat                 MLStrategy
-------------------  ------------
Start                2006-04-25
End                  2025-05-28
Risk-free rate       0.00%

Total Return         19.86%
Daily Sharpe         1.03
Daily Sortino        1.74
CAGR                 0.95%
Max Drawdown         -14.24%
Calmar Ratio         0.07

MTD                  5.72%
3m                   4.60%
6m                   1.22%
YTD                  1.22%
1Y                   -2.09%
3Y (ann.)            1.08%
5Y (ann.)            2.16%
10Y (ann.)           0.38%
Since Incep. (ann.)  0.95%

Daily Sharpe         1.03
Daily Sortino        1.74
...
Avg. Up Month        3.83%
Avg. Down Month      -0.62%
Win Year %           47.37%
Win 12m %            12.33%</code>

Я думаю, это доказывает, что фундаментальный анализ на форексе в целом работают.

Можете посмотреть полную версию кода тут.

  • обсудить на форуме:
  • EURUSD
307

Читайте на SMART-LAB:
Фото
Американские акции. Что выбрать инвестору в год рекордов?
Американский рынок акций ставит рекорды, но за фасадом бурного роста зреют тревожные сигналы — особенно в секторе технологий и...
Фото
🌾💼 ГК «Азот» проведет презентацию для инвесторов
📅  17 декабря в 11:00 МСК встречаемся с одним из лидеров по производству азотных удобрений и капролактама в России. 🔥 Почему стоит заглянуть...
Фото
МТК АО «Ресейл-АйТи» получило государственную поддержку
🚀 В 2025 году АО «Ресейл-АйТи» (МГКЛ владеет 77% акций компании) стало одной из трёх малых технологических компаний, получивших государственную...

теги блога FineFolio

....все тэги



UPDONW
Новый дизайн